Python クローリング&スクレイピング Vol.24 – クローリングとスクレイピングの分離

こんばんは!
T.R.Imaginationの北野です!

Python クローリング&スクレイピング
今回は第7章「クローラーの継続的な運用・管理」の中の

7.3 クローリングとスクレイピングの分離
7.3.1 メッセージキューの使い方
7.3.2 メッセージキューによる連携
を読みました!

読みました、というか
読んでいる途中で、解決できないエラーにぶち当たってデバック中です(汗

クローリングしたらAWSのSQSにメッセージキューを送っておいて、
pyqsライブラリを使ってスクレイピング処理を別で行うということをやってます。

import re
from pyqs import task
import lxml.html
from pymongo import MongoClient


@task(queue='ebook')
def scrape(key: str):
    """
    ワーカーで実行するタスク。
    """
    client = MongoClient('localhost', 27017)    # ローカルホストのMongoDBに接続する。
    html_collection = client.scraping.ebook_htmls   # scrapingデータベースのebook_htmlsコレクションを得る。
    ebook_html = html_collection.find_one({'key': key}) # MongoDBからkeyに該当するデータを探す。
    ebook = scrape_detail_page(key, ebook_html['url'], ebook_html['html'])
    
    ebook_collection = client.scraping.ebook_htmls  # ebooksコレクションを得る。
    
    # keyで高速に検索できるように、ユニークなインデックスを作成する。
    ebook_collection.create_index('key', unique=True)
    
    # ebookを保存する。複数回実行してもエラーにならないようにupsertを使用する。
    ebook_collection.update_one({'key': key}, {'$set': ebook}, upsert=True)


def scrape_detail_page(key: str, url: str, html: str) -> dict:
    """
    詳細ページのResponseから電子書籍の情報をdictで取得する。
    """
    root = lxml.html.fromstring(html)
    ebook = {
        'url': url,  # URL
        'key': key,  # URLから抜き出したキー
        'title': root.cssselect('#bookTitle')[0].text_content(),  # タイトル
        'price': root.cssselect('.buy')[0].text.strip(),  # 価格(strip()で前後の空白を削除)
        'content': [normalize_spaces(h3.text_content()) for h3 in root.cssselect('#content > h3')],  # 目次
    }
    return ebook  # dictを返す。


def normalize_spaces(s: str) -> str:
    """
    連続する空白を1つのスペースに置き換え、前後の空白を削除した新しい文字列を取得する。
    """
    return re.sub(r'\s+', ' ', s).strip()

コードの内容はこんな感じですが、
これをAWS側で実行すると

[INFO]: Loading Queues:
[INFO]: [Queue] ebook
[INFO]: Found matching SQS Queues: ['https://ap-northeast-1.queue.amazonaws.com/137117274772/ebook']
[ERROR]: Task scraper_tasks.scrape raised error in 0.0035 seconds: with args: ['978-4-297-11475-6'] and kwargs: {}: Traceback (most recent call last):
File "/home/ubuntu/crawler/scraping/lib/python3.7/site-packages/pyqs/worker.py", line 221, in process_message
task(*args, **kwargs)
File "/home/ubuntu/crawler/scraper_tasks.py", line 15, in scrape
ebook = scrape_detail_page(key, ebook_html['url'], ebook_html['html'])
TypeError: 'NoneType' object is not subscriptable

こんなエラーが出るんですよねー。

TypeError: 'NoneType'

型指定がされてないってことなんですかね。
でも

def scrape_detail_page(key: str, url: str, html: str) -> dict:

ってことで型指定はしてるんです。

どこを見たら良いのやら。。

デバッグ、頑張ります!