byh01337
@byh01337 (Geng Tanaka)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

pythonで書いたスクレイピングプログラムがエラーを出さずに落ちてしまいます

解決したいこと

Googleでニュースを検索するpythonコードを書こうとしています。

 macOS Catalina 10.15.7 / ターミナル 2.10 / python 3.9.7

  下に載せましたサンプルを走らせますと次のケースのいずれかで止まり、最後まで検索を終了できません。エラーも出力されず、これらへの対処法を調べることができないまま手を束ねています。

1) 159,160行でページ読み込みがタイムアウトになった場合、あきらめて次のページに行くように書いたつもりですが、プログラムがここで止まってしまいます("AA"までを表示するが、"AAA"を表示しない)。

driver.get(link)
wait.until(EC.presence_of_all_elements_located)

2)  180,181行で、プログラム止まってしまいます("BBB"までを表示するが、"BBBBBB"を表示しない)。

html = driver.page_source
wait.until(EC.presence_of_all_elements_located)

 上の2件を含め、他にもお気づきの点をご指摘いただけましたら幸いです。

発生している問題・エラー

(エラーがターミナルに表示されません。)

該当するソースコード

# -*- coding: utf-8 -*-

"""""""""""""""
#使用例;$ python google_news_step.py 環境 after:1990-01-01 before:1990-12-31 > log.txt
"""""""""""""""

import sys
#sys.path.append('/Applications/anaconda3/lib/python3.9/site-packages')
sys.path.append('/Volumes/Dropbox/Dropbox/study_work/webdriver')

### 【Selenium】PythonでYahooニュースの一覧を取得
### https://teshi-learn.com/2020-09/python-scrayping-driver/

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options

from bs4 import BeautifulSoup

from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains

import time

"""""""""""""""
#作業フォルダ指定
"""""""""""""""

options = Options()
#options.add_argument('--headless')
#driver = webdriver.Chrome("chromedriver.exe", options=options)
CHROMEDRIVER = "/Volumes/Dropbox/Dropbox/study_work/webdriver/chromedriver"
from selenium.webdriver.chrome import service as fs
chrome_service = fs.Service(executable_path=CHROMEDRIVER) 

import os
iDir = os.getcwd()
options = webdriver.ChromeOptions()
options.add_experimental_option("prefs", {"download.default_directory": iDir })
# ヘッドレスモード
options.add_argument('--headless')
# 「暫定的なフラグ」らしい。
options.add_argument('--disable-gpu')
# セキュリティ対策などのchromeに搭載してある保護機能をオフにする。
options.add_argument('--no-sandbox')
# ディスクのメモリスペースを使う。
options.add_argument('--disable-dev-shm-usage')
# リモートデバッグフラグを立てる。←これは✕
#options.add_argument('--remote-debugging-port=9222')
driver = webdriver.Chrome(service=chrome_service, options=options)

#ポップアップブロック機能の無効化
options.add_argument('--disable-popup-blocking')

# 最大の読み込み時間を設定
wait = WebDriverWait(driver=driver, timeout=30)
driver.implicitly_wait(30) # seconds# ページロードされるまでのタイムアウトを 5秒 に設定
driver.set_page_load_timeout(30)

#指定のWebサイトに移動
driver.get('https://www.google.com/?hl=ja')

# selenium.webdriver.support.ui の Select をimport
from selenium.webdriver.support.ui import Select
# selenium.webdriver.common.alert の Alert をimport
from selenium.webdriver.common.alert import Alert
#
from selenium.webdriver.common.by import By

"""""""""""""""
#キーワードと年代を入力
"""""""""""""""
key_word_list = sys.argv
key_word_list.pop(0)
key_word = " ".join(key_word_list)
print('key_word:', key_word)
element = driver.find_element(By.XPATH, "/html/body/div[1]/div[3]/form/div[1]/div[1]/div[1]/div/div[2]/input")
element.send_keys(key_word)

"""""""""""""""
#Google検索ボタンを押す
"""""""""""""""
element = driver.find_element(By.XPATH, "/html/body/div[1]/div[3]/form/div[1]/div[1]/div[3]/center/input[1]")
element.click()

"""""""""""""""
#「ニュース」ボタンを押す
"""""""""""""""
element = driver.find_element(By.XPATH, "/html/body/div[7]/div/div[4]/div/div[1]/div/div[1]/div/div[2]/a")
element.click()
	
"""""""""""""""
#手続き(検索ページを遷移しながらタイトルとURLを収集)
"""""""""""""""
import requests

def text_collect(driver):

    title_list = []     # タイトルを格納する空リストを用意
    link_list = []      # URLを格納する空リストを用意
    
    # ウィンドウハンドルを取得する
    handle_array = driver.window_handles
 
    # タイトルとURLを取得しながら検索ページの遷移
    while 1:
        
        # リンク('a href')とタイトル('h3')の入ったHTMLを抽出(クラス名で)→アプデで変わる可能性あり
        class_group = driver.find_elements(By.CLASS_NAME, 'ftSUBd')
        html = driver.page_source
 
        # リンクとタイトルを抽出しリストに追加するforループ
        for elem in class_group:

            try:
 				
                # タイトル
                title = elem.find_element(By.CLASS_NAME, 'mCBkyc').text
                
                # リンク
                link = elem.find_element(By.TAG_NAME, 'a').get_attribute('href')
                
            except Exception as e:
                #print(e)
                continue
            
            # 動画、画像、説明, 「他の人は〜」といった記事とは関係ないコンテンツを排除
            if title != '動画' and title != '画像' and title != '説明':
                if title != '':
                    
                    # URLを指定して、新しいタブを開く# 新しいタブを作成する
                    driver.execute_script("window.open()")
                    
                    # 新しいタブに切り替える
                    driver.switch_to.window(driver.window_handles[-1])
                    
                    try:
                        alert = driver.switch_to_alert()
                        print(alert.text)
                        alert.accept()
                    except:
                        #print("no alert to accept")
                    	pass
                    
                    # 新しいタブでURLアクセス
                    try:
                        print("A")
                        print(link)
                        wait.until(EC.presence_of_all_elements_located)
                        print("AA")
                    except Exception as e:
                        #print(e)
                        continue
                    
                    # 新しいタブでURLアクセス
                    try:
                        driver.get(link)
                        wait.until(EC.presence_of_all_elements_located)
                        print("AAAA")
                    except Exception as e:
                        #print(e)
                        continue
                    
                    # 新しいタブでURLアクセス
                    try:
                        driver.refresh()
                        #wait.until(EC.presence_of_all_elements_located)
                        print("AAAAA")
                    except Exception as e:
                        #print(e)
                        continue
                        
                    time.sleep(3)
                        
                    #ページソースの取得
                    try:
                        print("BBB")
                        html = driver.page_source
                        wait.until(EC.presence_of_all_elements_located)
                        print("BBBBBB")
                    except Exception as e:
                        #print(e)
                        continue

                    print("CCC")
                    #BeautifulSoup形式で取得
                    soup = BeautifulSoup(html, 'html.parser')
                    #テキストのみを取得=タグは全部取る
                    text=soup.get_text()

                    # textを改行ごとにリストに入れて、リスト内の要素の前後の空白を削除
                    lines= [line.strip() for line in text.splitlines()]
                    # 【補足】ここはリスト内包表記です。書き下すと以下のことをやっています。
                    # lines=[]
                    # for line in text.splitlines():
                    #lines.append(line.strip())
                    #print(lines)
                    #リストの空白要素以外をすべて文字列に戻す
                    text="\n".join(line for line in lines if line)
                    print("CCCCCC")
                    
                    #このリンクの解析結果を出力
                    title_list.append(title)
                    link_list.append(link)
                    print (title)
                    print (link)
                    print (text)
                        
                    #time.sleep(5)
                    wait.until(EC.presence_of_all_elements_located)
                    driver.close()
                        
                    #前のタブに切り替え
                    driver.switch_to.window(driver.window_handles[0])

        # 次ページがあれば遷移する
        try:
            driver.find_elements(By.XPATH, ("//*[text()='次へ']"))
            driver.find_element(By.XPATH, ("//*[text()='次へ']")).click()
            time.sleep(3)
        except Exception as e:
            #print(e)
            break
            
    return title_list, link_list
 
"""""""""""""""
#text_collect(driver)
"""""""""""""""
titles, links = text_collect(driver)
#print(ranking(driver))
 
# タイトルリストをテキストに保存
with open('title.txt', mode='w', encoding='utf-8') as f:
    f.write("\n".join(titles))
 
# URLリストをテキストに保存
with open('link.txt', mode='w', encoding='utf-8') as f:
    f.write("\n".join(links))
 
# ブラウザを閉じる
time.sleep(3)
driver.quit()

例)

def greet
  puts Hello World
end

自分で試したこと

$ python google_news_step-sample.py 環境
等を実行したものです(コードの中のコメントにある「年代」は気にしないでください)。

0

6Answer

同じメソッドを実行してるのに、成功したり失敗したりするようであれば、本件だと十中八九google側のbot判定アルゴリズムでaccess deniedになっていると思われます。
デバッグとしては、まずはシンプルにcurlでリクエストを連発してみて、どこかの閾値で200以外のレスポンスが返ってくるか確認してください。それでbanされているかは分かるはずです。

もしbanされている場合、対応としては

  • いくつかのproxyをローテーションする
  • リクエストの間隔を開ける
  • 偽装したuser-agentをローテーションする

など組み合わせて、相手の防御を掻い潜るポイントを見付ける必要があります。こうしたプロセスはrapid apiなどで月額数百円程度で売られている可能性があるので調べる価値はあります。

他の方がご指摘されている利用規約で禁止されている問題、については、もちろん自己責任ですが、個人的にはあまり気にする必要はないのかと思ってます。利用規約に違反することのリスクは、利用規約で約束された権利を剥奪されるのが関の山、なはずだからです。(当方法律家ではありませんが。)

rotots.txtに従うかどうかは使ってるライブラリによるかもしれません。pythonでスクレイピングするならscrapyをおすすめします。robots.txtに従うかどうかの設定も簡単に切り替えられるので。

1Like

logger をこれまでに知らず、今回ここでそれを教わったことは大きいです。ありがとうございます。

現在、ソースの冒頭に


import logging

logger = logging.getLogger("selenium.webdriver.remote.remote_connection")
logger.setLevel(logging.DEBUG)
s_handler = logging.StreamHandler()
s_handler.setFormatter(logging.Formatter("%(name)s🍎 %(message)s"))
f_handler = logging.FileHandler('test.log')
f_handler.setFormatter(logging.Formatter("%(name)s🍎 %(message)s"))

ログのコンソール出力の設定

logger.addHandler(s_handler)

ログのファイル出力先を設定

logger.addHandler(f_handler)


を加え、
$ python google_news_step.py 環境 after:2020-01-01 before:2020-12-31 >log.txt
を実行しましたところ、


...
selenium.webdriver.remote.remote_connection🍎 POST http://localhost:55846/session/9933484dba1954783f02ef174bf4b9d0/element/b531eb95-37ac-4443-bb24-5031726321e8/element {"using": "css selector", "value": ".mCBkyc", "id": "b531eb95-37ac-4443-bb24-5031726321e8"}
selenium.webdriver.remote.remote_connection🍎 Finished Request
selenium.webdriver.remote.remote_connection🍎 POST http://localhost:55846/session/9933484dba1954783f02ef174bf4b9d0/element/738cf9b7-2c65-45be-9ac2-604d0a03496e/element {"using": "css selector", "value": ".mCBkyc", "id": "738cf9b7-2c65-45be-9ac2-604d0a03496e"}
selenium.webdriver.remote.remote_connection🍎 Finished Request
selenium.webdriver.remote.remote_connection🍎 POST http://localhost:55846/session/9933484dba1954783f02ef174bf4b9d0/element/e9f9b51b-6481-4096-be8e-3882849f518f/element {"using": "css selector", "value": ".mCBkyc", "id": "e9f9b51b-6481-4096-be8e-3882849f518f"}
selenium.webdriver.remote.remote_connection🍎 Finished Request
selenium.webdriver.remote.remote_connection🍎 POST http://localhost:55846/session/9933484dba1954783f02ef174bf4b9d0/elements {"using": "xpath", "value": "//*[text()='\u6b21\u3078']"}
selenium.webdriver.remote.remote_connection🍎 Finished Request
selenium.webdriver.remote.remote_connection🍎 DELETE http://localhost:55846/session/9933484dba1954783f02ef174bf4b9d0 {}
selenium.webdriver.remote.remote_connection🍎 Finished Request


以上がコンソールに出力されたところでプログラムが終了しました。

DELETE http://localhost:55846/session/9933484dba1954783f02ef174bf4b9d0 {}
とはどういうものかネット検索で調べているのですが、なかなか理解できるページに当たりません。

 またここで識者の方々のアドバイスをいただけないでしょうか。

 なお、logging, loggerにつきましては

ほぼろ(@rhoboro)様の
「Pythonのライブラリが出力するログを活用しよう」
https://www.rhoboro.com/2020/12/29/logging-and-debug.html

長野透様の
「【Python入門】loggingモジュールで処理の記録を残してみよう!」
https://www.sejuku.net/blog/23149

を参考にさせていただきました。

 

1Like

以下気になる点です。

  • 実行したときのログはどんな感じでしょうか?もう少し情報あれば何か分かるかもしれませんので
  • あと、ご存じかもしれませんが、スクレイピングを禁止しているサイトもあるので、短い間隔でのアクセスが続いた場合に、うまく動かなくなる要素があるかもしれないかなとは思いました
    https://medpython.blogspot.com/2020/04/python-scraping-forbiddance-sites.html

参考までに、
私の環境では、該当のソースコードからCHROMEDRIVERのパスを変更したくらいで、以下の環境にて正常に動作することを確認できました。
Windows10 WSL2(Ubuntu 20.04 LTS) / Python 3.8.10

$ python3 scraping.py qiita
key_word: qiita
A
https://prtimes.jp/main/html/rd/p/000002093.000001348.html
AA
AAAA
AAAAA
BBB
BBBBBB
CCC
CCCCCC
テックトークイベント「Qiita Night」を、日本最大級の ...
https://prtimes.jp/main/html/rd/p/000002093.000001348.html
テックトークイベント「Qiita Night」を、日本最大級のエンジニアコミュニティ「Qiita」が定期開催!|エイチームのプレスリリース
プレスリリース・ニュースリリース配信サービスのPR TIMES
プレスリリースを受信
企業登録申請
・・・(省略)
0Like

logger を使ってログ表示をしてみるとかでしょうか.
Google 側から Session が切られているログが表示されるかも知れないです.

なお、marumen 様が記載されているように、Google 検索に対するスクレイピングは禁止されていたかと思います. (robots.txt を意図的に無視しないと動かないはず)

下記のスクレイピング練習サイトを使って、コードが正しいかチェックするという方法もあるかと思います
https://books.toscrape.com/index.html

0Like

robozushi10様、marumen様、kazukinagata様

 ご教示くださり感謝しております。体調をくずしてレスポンスが遅くなりました。

いただいた情報に目を通しながら自分の質問を読み返しますと、甚だ舌足らずで恥ずかしく思います。

 たとえば
$ python google_news_step.py 環境 after:2020-01-01 before:2020-12-31 >log.txt
としまして
log.txt
link.txt
title.txt
を得ますと、54件の検索結果が得られるのですが、手でGoogleで検索を行いますと200件以上がヒットする模様です。200件のうち54件しかページを当たれず、しかしエラー終了するわけでは無さそうで頭を抱えました。

 scrapy, loggerなど、頂いたキーワードを調べます。

0Like

recaptchaが表示されていたりはしませんか?seleniumではブラウザのスクリーンショットを残すこともできるので、これを見ることでデバッグが捗りそうです。

0Like

Your answer might help someone💌