【自分の勉強用メモ】クローラー開発の備忘録2024/08/18ゆきこ

# クローラー開発で活用する知識
:::note info
★やりたいこと：「次へ >」のボタンを押下して画面遷移したいです。※クリックしたい
以下が例です。

EC.element_to_be_clickable((By.LINK_TEXT, "Next"))
おそらく、「次へ >」でヒットするはずなのですが、スペースや半角・全角などを正しく設定してみてください。
それでダメなら、「次へ >」で試してみてください。

あるいは、以下のようにclass nameで取得してみてください。取得できない場合は、next以外でも試してください。

EC.element_to_be_clickable((By.CLASS_NAME, "next"))
②技術検証はなさらなくて大丈夫なのですが、ナンバリングの空白のところにナンバリングの数字を入れて、検索実行することって可能なのでしょうか？？

これは実際のページで動作確認しないと検証できません。実行できないのであれば不可能かと思われます。
:::

:::note warn
"★クエリパラメーターでページ遷移するには？
ページを開いたときに、URLにクエリパラメータとして、ページのNoのようなものはあるとページ遷移に活用できる。
もしあれば、その数字を変えてURLにアクセスすれば遷移できます。

※suumoを例にすると、page=2の部分がそれにあたります。
https://suumo.jp/jj/bukken/ichiran/JJ010FJ001/?ar=030&bs=011&ta=13&jspIdFlg=patternShikugun&sc=13106&sc=13107&sc=13108&sc=13118&sc=13121&sc=
13122&sc=13123&kb=1&kt=9999999&mb=0&mt=9999999&ekTjCd=&ekTjNm=&tj=0&cnb=0&cn=9999999&srch_navi=1&page=2

もしなければ、seleniumを使って、クリック動作を行う必要があります。
参考URL
https://www.seleniumqref.com/api/python/element_set/Python_click.html

:::

:::note 
★クエリパラメーターの補足情報メモ
「?term=」は、クエリパラーメタといって、Webサーバー側に渡す情報になります。
プログラムでいう、引数のようなものです。クエリパラーメタは、開発者側で定義します。そのため、URLにアクセスし、適当な文字を検索することで、「?term=」がついていることを確認しました。

■余談
URLにクエリパラメータを含めることで、誰がアクセスしても同じような結果が得られます。（GETメソッド）
一方、個人情報を入力するケースは、クエリパラメータにしてしまうと、情報が漏れてしまいます。情報を隠したいときは、POSTメソッドを使います。
:::

:::note
# ページネーション部分をスクレイピングする必要があるときは？
以下のプログラムを参考に、実装してみてください。

# ページ番号リンクをすべて見つける
page_links = soup.find_all('a', class_='Pager_link__rnYFP')

# ページ番号を抽出して整数に変換
page_numbers = [int(link.text) for link in page_links if link.text.isdigit()]

# 最大のページ番号を見つける
max_page_number = max(page_numbers)

print("最大のページ番号は:", max_page_number)
※レオパレス案件でページネーションの最大値の数字を読み取り実装したよ！メモゆきこ


:::note
# タグの修正方法と確認方法について
"前回のサイトと使われているタグが違うので、修正が必要です。

# 修正前
#booklists = soup.find_all('p', class_='title')  # p.title を全て取得

# 修正後
booklists = soup.find_all('div', class_='title')  # div.title を全て取得
調べ方としては、以前にもお伝えしている通り、print文でデバッグしていくのがよいです。

今回だと、
dataで値が取れているか確認【93行目にprint(data)を追記してコンソールを確認】
　→取れていない
a_tagで値が取れているか確認【67行目にprint(a_tag)を追記してコンソールを確認】
　→取れていない
booklistsで値が取れているか確認【62行目にprint(booklists)を追記してコンソールを確認】
　→取れていない、要素の取得から間違っている
という感じです。"
:::

:::note
# pyinstallerで音声を含めて配布したい

適宜、ファイル名などの変更が必要ですが、以下の流れでできると思います。

①音声ファイルをpythonのプログラムと同じフォルダに入れて、相対パスで参照させます。

import tkinter as tk
from tkinter import ttk, messagebox
import requests
from bs4 import BeautifulSoup
import pandas as pd
import os
import re
import pygame
import sys

# 音声ファイルの相対パスを設定
local_sound_file = os.path.join(sys._MEIPASS, '猫の鳴き声1.mp3')
②Specファイルを作成してます。

pyinstaller --name=my_application_name --onefile --windowed --noconsole your_script.py
③.specファイルを音声ファイルを指定するように修正します。

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(
    ['your_script.py'],
    pathex=[],
    binaries=[],
    datas=[('猫の鳴き声1.mp3', '.')],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='my_application_name',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=False,
    disable_windowed_traceback=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon=None,
)
④ビルド

pyinstaller your_script.spec
参考リンク
https://msteacher.hatenablog.jp/entry/2020/06/27/170529


:::
:::note
# ナンバリングの空白のところにナンバリングの数字を入れて、検索実行する


要素を取得して、値を入力すればよいです。
イメージは、以下の通りです。
# input要素を見つけて値を入力
input_element = driver.find_element(By.ID, 'input-id')  # 実際のinput要素のIDに置き換える
input_element.clear()  # 既存の値をクリア
input_element.send_keys('2')  # 値を入力


:::
:::note
# スクロールしたい①AP利用しない例
①スクロールを上から下までいき、＜リストの下まで到達しました・・・＞までデータ取得できる。

以下のコードの部分でfor i in range(5)としているため、5回しかスクロールしないようになっています。
while ループにして、＜リストの下まで到達しました・・・＞が表示されたらループを抜けるように修正が必要になります。

for i in range(5):
            html = driver.page_source
            soup = BeautifulSoup(html, 'html.parser')
            store_infomation = soup.select("div.bfdHYd.Ppzolf.OFBs3e")
            if len(soup(class_="HlvSq")) > 0 or len(store_infomation) > 25:
                return soup
            else:
                element = driver.switch_to.active_element
                try:
                    element.send_keys(Keys.END)
                except:
                    time.sleep(5)
                    element = driver.switch_to.active_element
                time.sleep(0.5)



:::
:::note
# スクロールしたい②API利用する例
①作成いただきましたコードで、スクロールのところはどこが効いているのでしょうか？

スクロールをするには、API実行時にpage_tokenを設定してあげる必要があります。修正後のプログラムは以下になります。

import googlemaps
import pandas as pd
import time

# APIキーを設定
API_KEY = 'YOUR_API_KEY'
gmaps = googlemaps.Client(key=API_KEY)

# 英語のtypesを日本語に変換する辞書
type_translation = {
    "cafe": "カフェ",
    "restaurant": "レストラン",
    "bar": "バー",
    "bakery": "ベーカリー",
    "lodging": "宿泊施設",
    "store": "店舗",
    "point_of_interest": "観光スポット",
    "establishment": "施設",
    # 必要に応じて他のtypesを追加
}

def translate_types(types):
    """typesのリストを日本語に変換する関数"""
    return [type_translation.get(t, t) for t in types]

def get_place_details(place_id):
    """指定されたplace_idの詳細情報を取得する関数"""
    fields = ['name', 'formatted_address', 'formatted_phone_number', 'rating', 'user_ratings_total']
    place_details = gmaps.place(place_id=place_id, fields=fields, language='ja')
    return place_details.get('result')

def search_places(query):
    """指定されたクエリで店舗を検索し、各店舗の詳細情報を取得する関数"""
    places_details = []
    next_page_token = None

    while True:
        if next_page_token:
            places_result = gmaps.places(query=query, page_token=next_page_token)
        else:
            places_result = gmaps.places(query=query)

        for place in places_result.get('results', []):
            place_id = place['place_id']
            details = get_place_details(place_id)
            types = place.get('types', [])
            translated_types = translate_types(types)
            places_details.append({
                '店舗名': details.get('name'),
                '住所': details.get('formatted_address'),
                '電話番号': details.get('formatted_phone_number'),
                '業種': ', '.join(translated_types),
                '評価': details.get('rating'),
                '口コミ数': details.get('user_ratings_total')
            })

        next_page_token = places_result.get('next_page_token')
        if not next_page_token:
            break

        # 次のページのトークンが有効になるまで少し待つ
        time.sleep(2)

    return places_details

# 例として、「カフェ 赤羽駅」を検索
query = 'カフェ 赤羽駅'
places = search_places(query)

# 結果をデータフレームに変換
df = pd.DataFrame(places)
print(df)

# エクセルファイルに出力
df.to_excel('places.xlsx', index=False)

print("エクセルファイル 'places.xlsx' に出力されました。")
なお、モジュールの仕様は、githubで確認できます。仕様を確認することでどのような引数が利用できるかわかります。
https://github.com/googlemaps/google-maps-services-python/blob/master/googlemaps/places.py

②こちらのコードにいろいろすることで、営業時間を取得することは可能でしょうか？

取得できるデータは、Place APIのレスポンスを確認して取得してみてください。
「opening_hours」を加工すれば、取れそうな気がします。
https://developers.google.com/maps/documentation/places/web-service/details?hl=ja#json

:::