中安拓也のブログ

プログラミングについて書くブログ

【Angular】オブジェクトの中身を表示する JSON パイプ

「TypeScript(Angular)でオブジェクトの中身を画面に表示したいのに[object Object]って表示される。どうしたらいい?」みたいな質問を仕事中に受けたのでメモ。

オブジェクトをJSON文字列に変換する

質問を受けた時は、JavaScriptのメソッドであるJSON.stringifyを使ってオブジェクトをJSON文字列に変換すると表示できますよーみたいに回答したんですが、後から調べたら、オブジェクトをJSON文字列にして表示するJsonPipeというのがAngularにあるとのこと。

試してみます

  • JsonPipeを使わない場合
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
  // 中身を表示したいオブジェクト
  public member = {
    id: 1,
    name: '大久保綾乃',
    age: 35,
    job: '小学校の教師'
  };

  constructor() {}

  ngOnInit() {
  }
}
<div>{{ member}}</div>

上記の実装で画面を表示すると、オブジェクトの中身は表示されず[object Object]と表示されてしまいます。

f:id:l08084:20191012153835p:plain
オブジェクトの中身が表示されない

  • JsonPipeを使った場合

特に事前準備は必要なく、テンプレート上で[中身を表示したい object ] | pipeと記載してあげるだけでOKです。

<div>{{ member | json }}</div>

このようにJSONパイプを使ってあげると、オブジェクトの中身が表示されます。

f:id:l08084:20191012154422p:plain
オブジェクトの中身が表示される

バージョン情報

  • Angular v7.2.0

【iPhone】Touch IDで表示されるダイアログのスクショをとる

f:id:l08084:20191009205757j:plain

iPhoneのTouch IDで表示されるダイアログって、ホームボタンに触ると消えてしまうので、いつものやり方だとスクショが取れない....

こんな時のために(?)、ホームボタンと電源ボタンの同時押し以外でスクリーンショットを撮る方法について整理します

バージョン情報

  • iPhone 8 plus
  • iOS 13.1.2

ホームボタンの代わりにAssistiveTouchを使う

AssistiveTouchを使うことで、ホームボタンを押下せずにスクリーンショットを撮ることができます。

AssistiveTouchを使ったスクショの撮影方法について説明します。

  1. 「設定」>「アクセシビリティ」>「タッチ」の順に選択し、「AssistiveTouch」を選択してオンにします

  2. 「最上位メニューをカスタマイズ」を選択し、アイコンを「スクリーンショット」だけにします

  3. 画面に表示されているAssistiveTouchをタップ

この手順でAssistiveTouchを使ってスクリーンショットを撮ることができます

参考サイト

iPhone、iPad、iPod touch で AssistiveTouch を使う - Apple サポート

Angular Materialでサイドメニューを作る

f:id:l08084:20191005175516p:plain
初期状態

上記画像の画面に、Angular Materialを使ってサイドメニューを追加します。

前提

下記の対応についてはすでに完了しているものとします。

  • Angular CLIによるAngularプロジェクトの作成
  • Angular Material を使用できるようにするための事前準備
  • ヘッダーの作成

バージョン情報

  • Angular v7.2.0
  • firebase: v6.3.4
  • Angular Material v7.3.7

開発

AppModuleの設定をした後、サイドメニューとメインコンテンツのスペースを作成し、最後にサイドメニューらしくなるようにリンクを追加します。

AppModuleの設定

まず事前準備として、Angular Materialのサイドメニューの部品であるMatSidenavModuleをAppModuleのimports:[]に設定します。

  • app.module.ts
// add this!
import { MatSidenavModule } from '@angular/material/sidenav';
// ...省略

@NgModule({
  imports: [
    // add this!
    MatSidenavModule,
    // ...省略
  ],
  // ...省略
})
export class AppModule {}

サイドメニューの雛形を作成

Sidenavの使い方ですが、まずサイドコンテンツとメインコンテンツの両方を<<mat-sidenav-container>タグ内に配置する必要があります。(ヘッダーなどのサイドメニューと関係ない項目は外側に配置してください)

そしてサイドコンテンツとして表示したい項目を<mat-sidenav>タグの内側に、メインコンテンツとして表示したい項目については<mat-sidenav-content>の内側に配置する必要があります。

  • home.component.html
<!-- ヘッダー -->
<app-header></app-header>

<!-- add this! -->
<mat-sidenav-container class="container">
  <!-- サイドメニュー -->
  <mat-sidenav mode="side" class="side-menu" opened></mat-sidenav>
  <!-- メイン コンテンツ -->
  <mat-sidenav-content></mat-sidenav-content>
</mat-sidenav-container>
  • home.component.scss
.container {
  position: absolute;
  top: 64px;
  bottom: 0;
  left: 0;
  right: 0;

  .side-menu {
    width: 128px;
  }
}

上記のコードを書いた時点で画面がこのようになります。

背景色のせいでわかりづらいですが...左側の白いスペースがサイドメニューになる部分で、右側の灰色のスペースがメインコンテンツを表示する部分になります。

f:id:l08084:20191006215549p:plain
サイドメニューのフレームだけできた状態

ナビゲーション(メニュー)の表示

最後に、サイドメニューにリンクを追加します。(サンプルなので、リンク先は設定していません)

まずメニューの作成にAngular MaterialのMatListModuleを使うので、AppModuleのimports:[]に設定します。

import { MatListModule } from '@angular/material/list';

@NgModule({
  // ...省略
  imports: [
    MatListModule,
  ],
  //... 省略
})
export class AppModule {}

ナビゲーションリンクはサイドコンテンツとして表示したいので、<mat-sidenav>の内側に配置します。

  • home.component.html
<!-- ヘッダー -->
<app-header></app-header>

<mat-sidenav-container class="container">
  <!-- サイドメニュー -->
  <mat-sidenav mode="side" class="side-menu" opened>
    <!-- add this! -->
    <mat-nav-list>
      <a mat-list-item [routerLink]="''"> Accounts </a>
      <a mat-list-item [routerLink]="''"> Create Account </a>
      <a mat-list-item [routerLink]="''"> Contacts </a>
      <a mat-list-item [routerLink]="''"> Create Contact </a>
      <a mat-list-item [routerLink]="''"> Activities </a>
    </mat-nav-list>
  </mat-sidenav>
  <!-- メイン コンテンツ -->
  <mat-sidenav-content></mat-sidenav-content>
</mat-sidenav-container>

上記のコードを書いた時点で、下記の画像のように、サイドメニューにリンクが表示されるようになります。

f:id:l08084:20191008205428p:plain
サイドメニュー完成

参考サイト

[AngularMaterial] Sidenavの使い方を理解しよう! - まんくつ

【Angular】Guardで認証されていないアカウントをブロックする

やりたいこと

  • ログイン(認証)を突破していないアカウントが、ログイン・アカウント登録画面以外にアクセスできないようにしたい
    • 現状だとURL(/home)を入力すると認証されていないユーザーでもホーム画面にアクセスできてしまう
  • ログインしている状態のアカウントがログイン・アカウント登録画面にアクセスできないようにしたい

前提

下記の対応についてはすでに完了しているものとします(対応内容の詳細については過去記事をご参照ください)

  • Angular CLIによるAngularプロジェクトの作成
  • メールアドレスとパスワードを用いたFirebase認証
  • ログアウト機能の実装
  • ログイン・ホーム画面などの作成

Guardを作成する

Angular CLIでプロジェクトを作成している場合は、下記のコマンドでauthentication.guard.tsファイルが作成されます。

$ ng g guard authentication

CLIで作成された初期状態のauthentication.guard.tsです。

  • authentication.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return true;
  }
}

canActivateメソッドでは、Guardを設定したURLに対してのアクセスを許可する場合はtrueを、アクセスを許可しない場合はfalseを返すようにします。

ログインしてないアカウントをホーム画面に入れない

空になっているcanActivateメソッドに、ログインしていないアカウントをホーム画面に遷移させない処理を書いていきます。

  • authentication.guard.ts
import { Injectable } from '@angular/core';
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  UrlTree,
  Router
} from '@angular/router';
import { Observable } from 'rxjs';
import { AngularFireAuth } from '@angular/fire/auth';
import { take, map } from 'rxjs/operators';

/**
 * ログインしていないアカウントをログイン画面に遷移させる
 *
 * @export
 * @class AuthenticationGuard
 * @implements {CanActivate}
 */
@Injectable({
  providedIn: 'root'
})
export class AuthenticationGuard implements CanActivate {
  constructor(private afAuth: AngularFireAuth, private router: Router) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ):
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree>
    | boolean
    | UrlTree {
    return this.afAuth.user.pipe(
      take(1),
      map(user => {
        if (user != null) {
          // ログインしていた場合userにユーザーの情報が入る
          return true;
        } else {
          // ログインしていない場合はログイン画面に遷移する
          this.router.navigate(['/login']);
          return false;
        }
      })
    );
  }
}

AngularFireAuth.userObservable<User|null>を返すため、usernullならばログインしていない、userが設定されていればログインしている、といった判定をすることができます。

作成したAuthenticationGuardAppRoutingModuleに設定するとGuardの実装が完了です。

  • app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './login/login.component';
import { SignUpComponent } from './sign-up/sign-up.component';
import { AuthenticationGuard } from './authentication.guard';

const routes: Routes = [
  { path: '', redirectTo: '/login', pathMatch: 'full' },
  { path: 'login', component: LoginComponent },
  {
    path: 'home',
    component: HomeComponent,
    canActivate: [AuthenticationGuard] // <- add this!
  },
  { path: 'sign-up', component: SignUpComponent }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

ログイン済みのアカウントをログイン画面に入れない

AuthenticationGuardでやったことの逆をやります。まず、Angular CLIでGuardファイルを作成します。

ng g guard authenticated

作成されたauthenticated.guard.tsファイルを使って、ログイン済みのアカウントがログイン・アカウント登録画面に遷移するのを禁止する処理を実装します。

  • authenticated.guard.ts
import { Injectable } from '@angular/core';
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  UrlTree,
  Router
} from '@angular/router';
import { Observable } from 'rxjs';
import { AngularFireAuth } from '@angular/fire/auth';
import { take, map } from 'rxjs/operators';

/**
 * ログイン済のアカウントをログイン・アカウント登録画面に、
 * 遷移させない
 *
 * @export
 * @class AuthenticatedGuard
 * @implements {CanActivate}
 */
@Injectable({
  providedIn: 'root'
})
export class AuthenticatedGuard implements CanActivate {
  constructor(private afAuth: AngularFireAuth, private router: Router) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ):
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree>
    | boolean
    | UrlTree {
    return this.afAuth.user.pipe(
      take(1),
      map(user => {
        if (user != null) {
          // ログインしていた場合はホーム画面に遷移する
          this.router.navigate(['/home']);
          return false;
        } else {
          // ログインしていない場合は遷移を許可する
          return true;
        }
      })
    );
  }
}

今度はusernullの場合に、Observable型のtrueを返して画面への遷移を許可しています。反面、usernullでないログイン済のアカウントの遷移は許可せずにホーム画面に遷移させています。

作成したAuthenticatedGuardloginsign-upのルーティングに設定すると、認証済のアカウントによるログイン・アカウント登録画面への遷移を禁止することができます。

  • app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './login/login.component';
import { SignUpComponent } from './sign-up/sign-up.component';
import { AuthenticationGuard } from './authentication.guard';
import { AuthenticatedGuard } from './authenticated.guard';

const routes: Routes = [
  { path: '', redirectTo: '/login', pathMatch: 'full' },
  {
    path: 'login',
    component: LoginComponent,
    canActivate: [AuthenticatedGuard] // <- add this!
  },
  {
    path: 'home',
    component: HomeComponent,
    canActivate: [AuthenticationGuard]
  },
  {
    path: 'sign-up',
    component: SignUpComponent,
    canActivate: [AuthenticatedGuard] // <- add this!
  }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

動作確認

当初の仕様通りにアクセスをブロックできているか確認します。

未ログインのアカウントをホーム画面に遷移させない

ログインしていない状態から、不正にURLを書き換えて、ホーム画面に遷移しようとします。

f:id:l08084:20191002191401p:plain
URLを`login`から`home`に変える

ホーム画面に遷移せずに、ログイン画面に戻されました。成功です。

f:id:l08084:20191002191609p:plain
ホーム画面に遷移せずにログイン画面に戻る

ログイン済のアカウントをログイン画面に遷移させない

今度はログイン済のアカウントを使って、ホーム画面からログイン画面への遷移を試みます。

f:id:l08084:20191002192137p:plain
URLを`home`から`login`に変える

URLをloginに書き換えて不正アクセスを試みますが、失敗します。こちらも成功です。

f:id:l08084:20191002192432p:plain
ガードされてホーム画面に戻る

バージョン情報

  • Angular v7.2.0
  • firebase: v6.3.4
  • Angular Material v7.3.7

参考サイト

angularfire/getting-started.md at master · angular/angularfire · GitHub

【Angular】Firebase認証からログアウトする

AngularとFirebaseを使ったログイン処理については、下記の記事ですでに実装済みのため、今回はログアウト処理を実装していきます。

関連記事

AngularでFirebase認証(その1) Firebaseのセットアップ - 中安拓也のブログ

AngularでFirebase認証(その2) Angular Materialを使ったログイン画面の作成 - 中安拓也のブログ

AngularでFirebase認証(その3) Firebase Authentication の呼び出し - 中安拓也のブログ

バージョン情報

CSSフレームワークとして、Angular Materialを使用しています。

  • Angular v7.2.0
  • firebase: v6.3.4
  • Angular Material v7.3.7

実装

すでに作成済みのヘッダーにログアウトボタンをつけた後に、そのボタンから呼び出されるサインアウト処理を実装します。

ヘッダーにログアウトボタンをつける

f:id:l08084:20190929194130p:plain
ヘッダーにログアウトボタン(右端)が表示されている
Material Iconexit_to_appアイコンを使ってログアウトボタンを作成します。

  • header.component.html
<mat-toolbar color="primary">
  <div class="main">
    <mat-icon class="header-icon">
      note
    </mat-icon>
    <span class="title">CONCEPT</span>
    <!-- ログアウトボタン -->
    <mat-icon (click)="signOut()" *ngIf="isSigningIn" class="right-icon">exit_to_app</mat-icon>
  </div>
</mat-toolbar>

*ngIf="isSigningIn"でログアウトボタンの表示・非表示の制御を、(click)="signOut()"でクリックイベント時にサインアウト処理を呼び出すように設定しています。

    <!-- ログアウトボタン -->
    <mat-icon (click)="signOut()" *ngIf="isSigningIn" class="right-icon">exit_to_app</mat-icon>

ヘッダーのSCSSファイル、.right-iconでログアウトボタンをヘッダーの右端に表示するように設定しています。

  • header.component.scss
mat-toolbar {
  .main {
    display: flex;
    align-items: center;
    width: 100%;

    .title {
      margin-left: 12px;
      font-size: 21px;
    }

    .header-icon {
      font-size: 24px;
    }

    // ログアウトボタンを右寄せにする
    .right-icon {
      margin: 0 0 0 auto;
    }
  }
}

ログアウト処理を実装する

ログアウトボタンのレイアウトについては実装できたので、肝心のロジックの部分を実装していきます。

ヘッダーのコンポーネントクラスです。

  • header.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { SpinnerService } from 'src/app/services/spinner.service';
import { Router } from '@angular/router';
import { AngularFireAuth } from '@angular/fire/auth';

/**
 * ヘッダーのコンポーネントクラス
 *
 * @export
 * @class HeaderComponent
 * @implements {OnInit}
 */
@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
  @Input() isSigningIn: boolean;

  constructor(
    private afAuth: AngularFireAuth,
    private router: Router,
    private spinnerService: SpinnerService
  ) {}

  ngOnInit() {}

  /**
   * ログアウト処理
   *
   * @memberof HeaderComponent
   */
  public signOut(): void {
    // スピナー表示
    this.spinnerService.show();
    // ログアウトAPIを呼び出す
    this.afAuth.auth
      .signOut()
      .then(() => {
        // ログアウトが成功したら、ログイン画面に遷移
        this.router.navigate(['/login']);
      })
      .catch(error => console.log(error))
      // 一連の処理が完了したらスピナーを消す
      .finally(() => this.spinnerService.hide());
  }
}

ログアウト処理の実装部分の抜粋です。

まず最初にスピナーの表示を実行し、AngularFireAuthライブラリを使って、Firebase AuthenticationのログアウトAPIを呼び出しています。
ログアウトAPIの処理が成功したら、Angular Routerによるルーティング処理でログイン画面に遷移します。その後、finally内でスピナーの非表示を実行します。

  public signOut(): void {
    // スピナー表示
    this.spinnerService.show();
    // ログアウトAPIを呼び出す
    this.afAuth.auth
      .signOut()
      .then(() => {
        // ログアウトが成功したら、ログイン画面に遷移
        this.router.navigate(['/login']);
      })
      .catch(error => console.log(error))
      // 一連の処理が完了したらスピナーを消す
      .finally(() => this.spinnerService.hide());
  }

これで実装は完了です。

動作確認

作成したログアウトボタンの動作を確認します。

f:id:l08084:20190929201033p:plain
ログアウトボタンを押下する

ログインした後に、ヘッダーのログアウトボタンをクリックすると....

f:id:l08084:20190929201214p:plain
ログアウトするとログイン画面に戻る

ログアウトした後、ログイン画面に戻ります。

【Angular】HttpInterceptorを使ってスピナーを表示する

f:id:l08084:20190922162235p:plain
スピナーが表示されている様子

はじめに

Angular4.3で追加されたHttpClientのInterceptorという機能を使うことで、HttpClientを使ったWebAPI呼び出しのタイミングで実行したい共通処理を定義することができます。

今回はこのInterceptor機能を使うことでAPIの呼び出し中にスピナーを表示する処理を実装します。

バージョン情報

CSSフレームワークとして、Angular Materialを使用しています。

  • Angular v7.2.0
  • firebase: v6.3.4
  • Angular Material v7.3.7

開発

スピナーコンポーネントとスピナーサービス、インターセプターを作成することでスピナーの表示処理を実装します。

HTTPリクエストを検知したインターセプターから受け取った、true/falseの値をスピナーサービス経由でスピナーコンポーネントに渡してスピナーの表示/非表示を切り替えるといった流れになります。

AppModuleの設定

まず、AppModuleのimports[]にAngular MaterialのProgress spinner と AngularのHttpClientModuleを追加します。

import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { HttpClientModule } from '@angular/common/http';
//...省略

@NgModule({
  //...省略
  imports: [
    HttpClientModule,
    MatProgressSpinnerModule,
  ],
  //...省略
})
export class AppModule {}

スピナーコンポーネントの作成

スピナーのコンポーネントクラスを作成していきます。

スピナーコンポーネントでは、インターセプターから渡させたbooleanの値をもとにスピナーの表示と非表示を実施します。

color, mode, valueはスピナーの色などを設定するAngular Material Spinnerのプロパティになります。

isLoadingはスピナーの表示・非表示を制御する変数で詳細は後述します。

スピナーのコンポーネントクラス

スピナーコンポーネントのHTMLファイルです。

isLoadingSubject型なので、コンポーネント側でsubscribeするかasyncパイプを使用する必要があります。

スピナーコンポーネントクラスのテンプレートファイル

スピナーコンポーネントのレイアウトを設定するSCSSファイルです。

スピナーコンポーネントクラスのSCSSファイル

スピナーサービスの作成

続いてスピナーサービスを作成します。

showhideメソッドを経由して、isLoadingtrue/falseを渡すことで、スピナーの表示・非表示を切り替えています。

スピナーのサービスクラス

インターセプターの作成

インターセプターSpinnerInterceptorを作成します。

HTTP リクエストが始まったタイミングでSpinnerServiceshowメソッドを呼び出してスピナーを表示し、リクエストが完了したタイミングでhideメソッドを呼び出してスピナーを消しています。

スピナーの表示・非表示を制御するインターセプター

AppModuleの設定

AppModuleのproviders: []に作成したスピナーサービスとインターセプターを追加します。

  providers: [
    SpinnerService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: SpinnerInterceptor,
      multi: true
    }
  ],

最後にapp.component.htmlにスピナーコンポーネントを追加して、スピナーの実装は完了です。

<app-spinner></app-spinner>
<router-outlet></router-outlet>

動作確認

実装が正しくできているか確認します。

HttpClientによるHTTPリクエスト

ここまでの実装が正しくできていれば、HttpClientを介してHTTPリクエストを実施するとスピナーが表示され、リクエストが完了するとスピナーが消えます。

下のコードでは、yesno.wtf APIをHttpClientで呼び出しているため、リクエストの間、スピナーが表示されます。

// ...省略
import { HttpClient } from '@angular/common/http';

export class LoginComponent implements OnInit {

  constructor(
    private http: HttpClient
  ) {}

  public ngOnInit() {
    this.http
      .get(`https://yesno.wtf/api`)
      .subscribe(response => console.log(response));
  }

HttpClientを使わない場合

Firebaseへのアクセスなどの、Angular HttpClientを使用しないHTTPアクセスの場合は、インターセプターが反応しません。そのため、直接SpinnerServiceのメソッドを叩いてスピナーを表示・非表示する必要があります。

下記のFirebase認証を実施しているコードでは、処理が開始したタイミングでshowメソッドでスピナーを表示し、処理が完了(finally)したタイミングでhideメソッドを呼び出すことでスピナーを消しています。

  public onSubmit() {
    // メールアドレスとパスワードをFirebase Authenticationに渡す
    // スピナーを表示する
    this.spinnerService.show();
    this.afAuth.auth
      .signInWithEmailAndPassword(
        this.emailControl.value,
        this.passwordControl.value
      )
      // ログインに成功したらホーム画面に遷移する
      .then(user => {
        this.router.navigate(['/home']);
      })
      // ログインに失敗したらエラーメッセージをログ出力
      .catch(error => {
        console.log(error);
        this.apiErrorMessage = error ? error.message : undefined;
      })
      // 処理が完了したら、スピナーを非表示にする
      .finally(() => this.spinnerService.hide());
  }

参考サイト

Display a loader on every HTTP request using Interceptor in Angular 7 - First Class JS

【Python】楽天カードの利用明細をダウンロードする

前回の記事

www.l08084.com

前回に引き続き、クレジットカードの利用明細のダウンロードをしていきます。

前回は、UIテスト自動化ツールのSeleniumuとChrome Driverをインストールして、ドコモのクレジットカードであるdカードの利用明細をダウンロードするところまでやりました。

バージョン情報

PCはmacOS Mojave(10.14.6)

  • selenium: 3.141.0
  • chrome: 76.0
  • ChromeDriver: 76.0
  • Python 3.7.3

楽天カードの利用明細をダウンロードする

f:id:l08084:20190915175346p:plain
ブラウザを自動操縦して利用明細をダウンロードしている様子
今回は、楽天カードの利用明細を引き続きPythonを使ってダウンロードしていきます。

今回は利用明細のダウンロードにrequestsモジュールを使用するため、ターミナルを開いて下記のコマンドを実行する必要があります。

$ pip install requests

続いて、Chromeを自動操縦して楽天カードの利用明細をダウンロードするPythonのコードを書きます。

楽天カードの利用明細をダウンロードするコード

前回同様、楽天カードサイトのユーザIDとパスワードはrakuten_credentials.pyファイルに別途定義しています。

苦労した点

f:id:l08084:20190915174543p:plain
利用明細CSVのダウンロードリンク

<a href="XX" class="stmt-c-btn-dl stmt-csv-btn">

利用明細のCSVリンクを取得する時に、CSSのクラス名からアンカータグのDOMを取得しようとしましたが、複数のclassが設定されていたため、どのメソッドで取得できるかがわかりませんでした。

python - Selenium Compound class names not permitted - Stack Overflow

この問題は上記のstackoverflowのサイトで解決しました。CSSのclassが二つ以上ある要素をSeleniumのfind_element_by_class_nameで取得しようとすると失敗しますが、代わりにfind_element_by_css_selectorを使えば取得できます。

tag = driver.find_element_by_css_selector('.stmt-c-btn-dl.stmt-csv-btn')

参考サイト

ゼロからはじめるPython(49) Pythonでブラウザ自動操縦してカード明細を自動でダウンロードしよう(その1) | マイナビニュース

ゼロからはじめるPython(50) Pythonでブラウザ自動操縦してカード明細を自動でダウンロードしよう(その2) | マイナビニュース