質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.23%

Seleniumを使ってスクレイピングをしCSVリスト化したいのですが、正しいリスト化/タグの指定ができません。

受付中

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 243

HF0603

score 5

前提・実現したいこと

現在、以下のサイトの必要箇所をスクレイピングしてCSVリスト化しようと試みております。

対象サイト
https://travelersnavi.com/coupon/category/waribiki

並んでいる記事のタイトル・投稿された日付を取得したいのですが、
日付箇所のタグをどう指定すればいいのかがわかりません。

イメージ説明

該当のソースコード

from selenium import webdriver
import chromedriver_binary
import time
import pandas as pd

TITLE_LIST = []
DAY_LIST = []

browser = webdriver.Chrome()
for num in range(1,3):
    browser.get('https://travelersnavi.com/coupon/category/waribiki/page/{}'.format(num))

    campaign_title = browser.find_elements_by_tag_name('h3')
    campaign_day = browser.find_elements_by_css_selector('.blog_info > p')

    for title in campaign_title:
        TITLE_LIST.append(title.text)
    for day in campaign_day:
        DAY_LIST.append(day.text)
    time.sleep(5)

print(TITLE_LIST)
print(DAY_LIST)
browser.close()

df = pd.DataFrame()
df['TITLE'] = TITLE_LIST
df['DAY'] = DAY_LIST

df

df.to_csv('旅行キャンペーン一覧.csv',index=False)

発生している問題・エラーメッセージ①

printまでは可能なものの、
ValueError: Length of values does not match length of index
のエラーが出てしまいます。

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-8-21a1eab37410> in <module>
     25 df = pd.DataFrame()
     26 df['TITLE'] = TITLE_LIST
---> 27 df['DAY'] = DAY_LIST
     28 
     29 df

~/opt/anaconda3/lib/python3.7/site-packages/pandas/core/frame.py in __setitem__(self, key, value)
   3470         else:
   3471             # set column
-> 3472             self._set_item(key, value)
   3473 
   3474     def _setitem_slice(self, key, value):

~/opt/anaconda3/lib/python3.7/site-packages/pandas/core/frame.py in _set_item(self, key, value)
   3547 
   3548         self._ensure_valid_index(value)
-> 3549         value = self._sanitize_column(key, value)
   3550         NDFrame._set_item(self, key, value)
   3551 

~/opt/anaconda3/lib/python3.7/site-packages/pandas/core/frame.py in _sanitize_column(self, key, value, broadcast)
   3732 
   3733             # turn me into an ndarray
-> 3734             value = sanitize_index(value, self.index, copy=False)
   3735             if not isinstance(value, (np.ndarray, Index)):
   3736                 if isinstance(value, list) and len(value) > 0:

~/opt/anaconda3/lib/python3.7/site-packages/pandas/core/internals/construction.py in sanitize_index(data, index, copy)
    610 
    611     if len(data) != len(index):
--> 612         raise ValueError("Length of values does not match length of index")
    613 
    614     if isinstance(data, ABCIndexClass) and not copy:

ValueError: Length of values does not match length of index
これに対する仮説

以下の部分でタイトルの数と日付の数がリンクしておらず、
Dateframeへの格納が上手くできていないためのエラーと見ております。

df = pd.DataFrame()
df['TITLE'] = TITLE_LIST
df['DAY'] = DAY_LIST

これを解消してリスト化するためには、

  • DAY_LISTで不要に抽出してしまっている箇所を読み込んでこないよう抑制する
  • 逆にTITLE_LISTの不足分をNULLとして抽出するようにする
    なのかと思いますが、こちらの方法がわかりません。

発生している問題・エラーメッセージ②

仮にエラーメッセージ①が解決したとしても、
このままのCSSセレクター指定だと、指定した日付以外のタグ付けテキストも一緒に抽出されてしまいます。
以下、printした時点での抽出結果です。

['東京ディズニーリゾート周辺ホテルに使える宿泊割引クーポン特集', 'USJ周辺ホテル予約に使える旅行クーポン特集', '北海道ふるさと割旅行クーポンで宿泊・ツアー割引', '箱根伊豆ふっこう割・旅行クーポンでホテル旅館の宿泊割引!観光復興応援', '楽天カーボン・オフセットキャンペーンクーポンで宿泊2,000円引|1名利用OK', '宮崎県ふるさと割旅行クーポンで宿泊割引', 'dトラベル宿泊dポイント7%増量、ふっこう割クーポン併用可', '鳥取・米子へのANAツアー旅行クーポンで最大6万円割引&レンタカーもお得', '滋賀県ふるさと割クーポンで旅行最大5,000円割引|大河ドラマ麒麟がくる放映', 'JTB・るるぶ・dトラベル全国厳選宿で使える旅行クーポン', '旅行クーポンサイト|ふっこう割・国内ホテル・ツアー割引情報まとめ']
['2020/2/4    じゃらん, まとめ, インターネット予約, ヤフートラベル, 楽天トラベル', '2020/2/4    じゃらん, るるぶ, インターネット予約, ヤフートラベル, 楽天トラベル', '2020/2/3    るるぶ, インターネット予約', '2020/2/3    インターネット予約, 楽天トラベル', '2020/2/7    インターネット予約, 楽天トラベル', '2020/2/3    じゃらん, ふるさと割, インターネット予約', '2020/2/1  ', '2020/1/31    ふるさと割, インターネット予約, 楽天トラベル, 飛行機', '2020/1/31    るるぶ, インターネット予約', '2020/1/31    るるぶ, インターネット予約, ヤフー', '2020/2/13', '2020/2/13', '2020/2/13', '2020/2/12', '2020/2/12']

今回抽出したい要素は「#text」ですが、どう指定すればここのみ抜き出せるのかがわからないです。

これに対する仮説

以下サイトを参考に要素を指定しました。

https://kurozumi.github.io/selenium-python/locating-elements.html

こちらを拝見する限りだと、
タグを指定できない場合、XPathを使用すると描かれており、
もし現状タグ指定ができないのであれば、XPathを使用する方法で解決するのでしょうか?

  XPathを使用する主な理由の1つは、探したい要素に適切なIDまたは名前属性がない場合です。XPathを使用すると、絶対的な用語(アドバイスされていない)で要素を見つけることも、idまたはname属性を持つ要素に関連付けることができます。XPathロケータを使用して、idやname以外の属性を使用して要素を指定することもできます。

まとめ

一つのケースで二つ質問する形となってしまい大変恐縮なのですが、
ゴールとしては冒頭挙げた通り、「並んでいる記事のタイトル・投稿された日付」の二箇所を取得してCSVリスト化できれば問題ございません。

わかりづらい点等あるかと思いますが、ご教授いただけますと幸いです。

補足情報(FW/ツールのバージョンなど)

開発環境/使用ライブラリ
・macOS Mojave 10.14.6
・python3.7
・anaconda3 jupyter notebook
・Googlechrome Version: 76.0.3809.126.0
・Selenium (chromedriver-binary==79.0.3945.36.0)
・pandas

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

+1

pandasエラー

開発者ツールに

document.getElementsBytagName('h3')
document.getElementsByClassName('blog_info')

とそれぞれ打ち込むと11個の配列と15個の配列が出てきます。
4つの差は何かというと「お得に旅する最新情報」です。

なので正解は以下の通りです。

main_articles = browser.find_element_by_tag_name('main')
campaign_title = main_tag.find_elements_by_tag_name('h3')
campaign_day = main_tag.find_elements_by_css_selector('.blog_info > p')

日付のみを取得できない

こんなもんsplitなり正規表現なりで切ってやればいいだけです。

import re

for day in campaign_day:
    nen = day.split('/')[0]
    tsuki = day.split('/')[1]
    hi = re.sub('\\D', '', day.split('/')[2])
    DAY_LIST.append(nen + '/' + tsuki + '/' hi)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/02/15 10:55

    ご丁寧な回答ありがとうございます。
    pandasについては頂いた形で解決しました。
    ※エラーが出たのでmain_tag.をmain_articlesに変えたところ抽出できました。

    main_articles = browser.find_element_by_tag_name('main')
    campaign_title = main_articles.find_elements_by_tag_name('h3')
    campaign_day = main_articles.find_elements_by_css_selector('.blog_info > p')

    正規表現ですが、Webelementのattributeエラーが出てしまいます。

    ---------------------------------------------------------------------------
    AttributeError Traceback (most recent call last)
    <ipython-input-43-8a9061cbdf94> in <module>
    19 TITLE_LIST.append(title.text)
    20 for day in campaign_day:
    ---> 21 nen = day.split('/')[0]
    22 tsuki = day.split('/')[1]
    23 hi = re.sub('\\D', '', day.split('/')[2])

    AttributeError: 'WebElement' object has no attribute 'split'

    以下のサイトなどを参考に調べてみましたが、すべてを理解しきれず…
    https://qiita.com/VDiUZnM1hUIzKvb/items/4d18ca1d781ed6ff2b2f
    見当つきそうな要因をご教授くだされば幸いです。

    キャンセル

  • 2020/02/16 23:49

    すみません思いっきり間違えていました。

    day.text.split('/')[0]でいけると思います。

    キャンセル

  • 2020/02/17 15:34

    Shirai様、御丁寧にありがとうございました。
    無事解決致しました。

    ※初学者につき初めてのteratail投稿でしたが、的確な回答いただき感動してしまいました。引き続き精進してまいります。本当にありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.23%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる
  • トップ
  • Pythonに関する質問
  • Seleniumを使ってスクレイピングをしCSVリスト化したいのですが、正しいリスト化/タグの指定ができません。