Web

続スクレイピング

投稿日:2021年7月24日 更新日:

seleniumライブラリを使ったスクレイピングについて、解説します。

サイトには、ブラウザとセッションを管理するものもあり、セッションがないとアクセスできないようにしているサイトもあります。セッションはCookieのデータとして設定されますが、HTTPSの場合、暗号化されていて単にHTTPリクエストを送信するだでは、スクレイピングできない場合もあります。

また、次ページを表示する際、URLやパラメータにページ番号を指定するような方法ではなく、「もっと見る」ボタンの押下によって次ページを表示させるサイトもあります。

このような場合、 seleniumライブラリ を使ってサイトにアクセスし、スクレイピングを実施します。

■seleniumライブラリとは

Seleniumとは各プログラム言語(Java/Python/C#/Ruby/JavaScript/Kotlin)からブラウザ(Chrome/FireFox/IE/Edge/Safari/Opera)を操作するためのライブラリなどの環境を提供するプロジェクトです。WebDriverと呼ばれる各ブラウザに対応した実行ファイルを経由して、各プログラム言語からブラウザを操作します。

https://www.selenium.dev/documentation/en/

Python+Chromeの場合、以下の環境が必要です。

1)Python
変更が容易にできるスクリプト言語を使います。その中でも、スクレイピングのための環境が揃っているPythonが使われます。
Pythonの環境として、Anacondaが有名です。主に機械学習用のパッケージですが、Python単独でインストールするよりも、主要な各種ライブラリが含まれたパッケージとなっているため便利です。

https://www.anaconda.com/products/individual


2)PythonのSeleniumライブラリ
以下を実行することでインストールできます。

 pip install selenium

2)Chromeブラウザ
以下からダウンロードし、インストールします。

https://www.google.com/chrome/

3)ChromeのWebDriver
以下からダウンロードし、実行ファイルを取得します。

https://chromedriver.chromium.org/downloads

注意点は、ChromeのバージョンにあったWebDriverを選んでダウンロードすることです。
Chromeのバージョン は、Chromeを立ち上げ、「ヘルプ→Google Chromeについて」を参照することで確認できます。本ブログの例では、ver92の「ChromeDriver 92.0.4515.43」を利用しています。


■ seleniumライブラリ を使ったスクレイピングの実例

以下の「■スクレイピングの実例」と原則、同じ流れです。

異なるのは、最初にWebDriverの初期化を実行し、最後にWebDriverの終了処理を実行する点と、 HTTPリスエストを送信し、HTTPレスポンスからHTMLを取得する関数がWebDriver対応となっている点です。

WebDriverの初期化処理では、Chromeの実行ファイルとChromeのWebDriverの実行ファイルを設定し、WebDriverのインスタンスを生成します。そして、WebDriverインスタンスを使って、HTTPリスエストを送信し、HTTPレスポンスからHTMLを取得します。

留意点は、requestsライブラリによって直接HTTPプロトコルを駆動する方法より、ずっと処理時間がかかることです。これは、件数が多いサイトの全データを取得する場合には問題になると思われます。
以下に、WebDriverに対応したサンプルコードを示します。
# -*- coding: utf-8 -*-
import sys
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time

# Seleniumライブラリのインポート
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import NoSuchElementException

#
# WebDriverの初期化関数 
#
def InitWebDrv():

    options = Options()
    # オプションに画面を表示せずに動作するモードを指定
    options.add_argument('--headless')
    # オプションにGoogleから暫定的に必要といわれているフラグを指定
    options.add_argument('--disable-gpu')

    #Chromeの実行ファイルのパスを設定
    options.binary_location = "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"
    #WebDriverの実行ファイルのパスを指定し、WebDriverのインスタンスを生成
    driver = webdriver.Chrome(chrome_options=options, executable_path="C:\\Users\\swata\\Desktop\\Python\\Python\\it\\chromedriver_win32\\chromedriver.exe")
        
    return driver


#
# WebDriverの終了関数
#
def EndWebDrv(driver):
    driver.close()
    driver.quit()


#
# HTTPリクエスト・レスポンスの送受信関数(WebDriver対応) 
#
def ConnectUrl_WebDrv(target_url):

    # WebDriver経由でChromeがサイトにアクセスする際、各ブラウザ毎・OS毎のUserAgentに設定する値のリスト
    df_ua = pd.DataFrame([
                                ['Chrome Win7','Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1'],
                                ['Chrome Win10','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'],
                                ['Firefox Win7','Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0'],
                                ['Firefox Win10','Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0'],
                                ['IE9','Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR'],
                                ['IE11','Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; rv:11.0) like Gecko'],
                            ],
                           columns=['name','ua'])

    data = ''
    ConnectRetryCount = 0

    while True:  #  無限ループ

        try:

            # HTTPリクエスト送信
            driver.get(target_url)
            # 受信したHTTPレスポンスからHTMLを取得
            data = driver.page_source

            break

        except:

            print('*****接続失敗*****\n'+str(target_url)+'\n')

            #接続失敗した場合、リトライを3回実施する
            if ConnectRetryCount < 3: 
                print('Wait 15Sec\n')
                #15秒待ってリトライ
                time.sleep(15) 
                ConnectRetryCount += 1
                continue
            else:
                break

    return data


#
# 求人詳細取得関数
#
def GetDetail(target_url):

    # 15秒待つ
    time.sleep(15)

    data = ConnectUrl_WebDrv(target_url)

    # BeautifulSoupでHTMLの要素を抽出
    soup = BeautifulSoup(data, 'lxml')

    # HTMLの要素からすべての「<div class="layout-table ">~</div>」のHTMLタグを取得    
    details = soup.find_all("div", attrs={"class": "layout-table"})

    company = ''
    work = ''
    license = ''
    place = ''
    salary = ''

    # すべての「<div class="layout-table ">~</div>」のHTMLタグの繰り返しループ
    for detail in details:

        # 個々の「<div class="layout-table ">~</div>」のHTMLタグからdtタグ,ddタグを全て取り出す
        dts = detail.find_all("dt")
        dds = detail.find_all("dd")

        # 個々のdtタグ,ddタグから必要なデータを取り出す
        for index, dt in enumerate(dts):
            dt_value = dt.text.strip()
            dd_value = dds[index].text.strip()

            if dt_value == '仕事内容':
                work = dd_value
                continue

            if dt_value == '応募資格':
                license = dd_value
                continue

            if dt_value == '勤務地':
                place = dd_value
                continue

            if dt_value == '年収':
                salary = dd_value
                continue

            if dt_value == '求人会社名':
                company = dd_value
                continue

    return company,work,license,place,salary


#
# メイン処理
#
if __name__ == "__main__":

    # 引数の取得(CSVを出力するファイル名を指定する)
    args = sys.argv
    if len(args) < 2:
        print('USAGE: GetDataNikkeiTech.py  <outputfile>')
        exit

    #CSVを出力するファイル名
    param_outputfile = args[1]

    # proxy_servers経由の場合、プロキシの指定をする必要がある
    '''
    proxies = {
    "http":"http://proxy.XXX.co.jp:8080",
    "https":"http://proxy.XXX.co.jp:8080"
    }
    '''

    # WebDriverの初期化
    driver=InitWebDrv()

    # PandasのDataFrameを作成する
    df = pd.DataFrame([],columns=['company','work','license','place','salary'])

    #ページ番号の開始を設定
    PageNo = 1
    #ページに含まれる件数を設定(50件)
    KENSU_PER_PAGE =50

    # データ取得の繰り返し
    TotalPage = 0
    while True:  

        # URLを指定(ページ番号を指定)
        target_url = 'https://tech.nikkeihr.co.jp/it/kyujin/ss_openweb_system/pg'+str(PageNo)+'/'

        # HTTPリクエスト・レスポンスの送受信 
        data = ConnectUrl_WebDrv(target_url)
        if len(data) == 0:
            break

        # BeautifulSoupでHTMLの要素を抽出
        soup = BeautifulSoup(data, 'lxml')

        if PageNo == 1:

            # 最初のページの場合
            # HTMLの要素から「<div class="pager-result ">~</div>」のHTMLタグを取得
            kensu_data = soup.find("div", attrs={"class": "pager-result"})

            # さらに「<span class="total">~</span>」のHTMLタグを取得
            kensu_data = kensu_data.find("span",attrs={"class": "total"})
            # さらに「<span class="num">~</span>」のHTMLタグを取得
            kensu_data = kensu_data.find("span",attrs={"class": "num"})
            # テキストから件数を取得
            kensu = kensu_data.text.strip()
            kensu = int(kensu)
            print(kensu)

            # ページあたりに含まれる件数から何ページ分取得すればいいかを算出する
            if (kensu % KENSU_PER_PAGE) ==0 :
                TotalPage = int((kensu / KENSU_PER_PAGE )) 
            else:
                TotalPage = int((kensu / KENSU_PER_PAGE )) + 1


        # 全タイトル部分のHTMLを取得
        title_data_all = soup.find_all("h2", attrs={"class": "title04"})

        # ページ内の全タイトル分の繰り返しループ
        for title_url in title_data_all:

            try:
                # 求人詳細URLを取得
                title_url = title_url.find("a") 
                title_url = title_url['href']
                detail_url = 'https://tech.nikkeihr.co.jp'+title_url

                company,work,license,place,salary = GetDetail(detail_url)

                # 仕事内容、会社名があるか判断
                if work != '' and company != '' :

                    #取得したデータ行を生成し、DataFrameに追加
                    row = pd.Series([company,work,license,place,salary], index=df.columns)
                    df = df.append(row,ignore_index=True)

            except:
                print('*****例外発生*****\n'+'継続\n')
                continue

        print('取得='+str(len(df))+' '+'ページ数='+str(PageNo)+'/'+str(TotalPage))

        #カウントアップ
        PageNo += 1

        #全ページ取得したらブレーク
        if PageNo > TotalPage :
            break

        # 15秒待って継続する
        time.sleep(15)

    #全ページ取得したら、CSV形式で保存
    if PageNo > TotalPage :
        df.to_csv(param_outputfile)
    else:
        #途中で中断した場合も、途中まででCSV保存
        df.to_csv(param_outputfile+'_STOP_AT_PAGENO_'+str(PageNo))


    # WebDriverの終了
    EndWebDrv(driver)

■WebDriverから「もっと見る」ボタンをクリックする

最後に、URLの引数でページ番号を指定して次ページが表示する方法でなく、「もっと見る」ボタン によって表示するサイトの場合でも、WebDriver経由で 「もっと見る」ボタン をクリックすることで次ページを表示できます。

以下にサンプルコードを示します。

# HTTPリクエスト送信
driver.get(target_url)

try:

    # 「もっと見る」ボタンをクラス名で探索
    while driver.find_element_by_class_name("m-read-more"):
        # 「もっと見る」ボタンのインスタンスを取得
        elem = driver.find_element_by_class_name('m-read-more')
        # 「もっと見る」ボタンをクリック
        elem.click()

        # HTMLデータが取得されるまで待つ
        time.sleep(5)

        # HTMLデータを取得されるまで待つ
        data = driver.page_source

    # Whileループの探索で「もっと見る」ボタンが見つからない場合=最終ページと判断し、例外をスルー
    except NoSuchElementException:
        pass


-Web

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連記事

決済API(PAY.JPの例)

EC(電子商取引)などインターネットを使ったサービスが増える中、取引の決済処理を担う決済APIも多種多様になってきています。それら決済APIについて、解説します。 ■ 決済APIとは何か 代表的な決済 …

スクレイピング

インターネットにあるWebサイトのHTMLデータの中から、必要なデータを自動的に取得することをスクレイピングといいます。Googleなどの検索エンジンがインデックスを作成するために、自動でWebサイト …

GCPでのWebサービスの作り方(Python、Flask、MYSQL、Mail、Apache) 1/2

GCP(Google Cloud Platform)上で、Webサービスを立ち上げるポイントを整理します。オンプレミス(自前でネインターネット接続環境やサーバを調達してシステムを構築)でWebサービス …

GCPでのWebサービスの作り方(Python、Flask、MYSQL、Mail、Apache) 2/2

以下からの続きとなります。GCPでのWebサービスの作り方(Python、Flask、MYSQL、Mail、Apache) 1/2 – DXインテリジェンス (itresourcetech.net) …

Webシステム

ネットワークを使ったシステムは、クライアント/サーバ型システムが基本です。クライアント/サーバ型システムとは、ユーザが操作するクライアントと、データを管理し、処理を実行するサーバとの間をネットワークで …