scrapyでYahooニュースをクローリング

これも書籍の通り。復習のためにメモを残しておく。 これが一番シンプルかつ実用的なサンプルといえる。

Scrapyの作法に沿ってプロジェクトを作る。

 ᐅ scrapy startproject myproject
 ᐅ cd myproject
 ᐅ scrapy genspider news news.yahoo.co.jp
 ᐅ tree myproject 
myproject
├── __init__.py
├── __pycache__
│   ├── __init__.cpython-36.pyc
│   ├── items.cpython-36.pyc
│   └── settings.cpython-36.pyc
├── items.py
├── middlewares.py
├── pipelines.py
├── settings.py
└── spiders
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-36.pyc
    │   └── news.cpython-36.pyc
    └── news.py

3 directories, 12 files

編集するのは、settings.py(設定ファイル), items.py(データのスキーマを定義する), spiders/news.py(スパイダーのロジックを記述)の3つ。

settings.py

(中略)
DOWNLOAD_DELAY = 1 #リクエストごとに1秒空ける
(中略)

items.py

import scrapy


class Headline(scrapy.Item):
    """
    ニュースのヘッドラインを表すItem
    """
    title = scrapy.Field()
    body = scrapy.Field()

spider/news.py

import scrapy

from myproject.items import Headline


class NewsSpider(scrapy.Spider):
    name = 'news'
    allowed_domains = ['news.yahoo.co.jp']
    start_urls = ['http://news.yahoo.co.jp/']

    def parse(self, response):
        """
        トップページのトピックス一覧から個々のトピックスへのリンクを抜き出してたどる。
        """
        for url in response.css('ul.topics a::attr("href")').re(r'/pickup/\d+$'):
            yield scrapy.Request(response.urljoin(url), self.parse_topics)

    def parse_topics(self, response):
        """
        トピックスのページからタイトルと本文を抜き出す。
        """
        item = Headline()
        item['title'] = response.css('.newsTitle ::text').extract_first()  # タイトル
        item['body'] = response.css('.hbody').xpath('string()').extract_first()  # 本文
        yield item
  ᐅ scrapy crawl news -o items.jl                                                    
2018-11-05 23:43:28 [scrapy.utils.log] INFO: Scrapy 1.5.1 started (bot: myproject)

(中略)

2018-11-05 23:43:43 [scrapy.core.engine] INFO: Closing spider (finished)
2018-11-05 23:43:43 [scrapy.extensions.feedexport] INFO: Stored jl feed (8 items) in: items.jl
2018-11-05 23:43:43 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 3455,
 'downloader/request_count': 13,
 'downloader/request_method_count/GET': 13,
 'downloader/response_bytes': 167867,
 'downloader/response_count': 13,
 'downloader/response_status_count/200': 11,
 'downloader/response_status_count/301': 2,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2018, 11, 5, 14, 43, 43, 741275),
 'item_scraped_count': 8,
 'log_count/DEBUG': 22,
 'log_count/INFO': 8,
 'memusage/max': 50143232,
 'memusage/startup': 50143232,
 'request_depth_max': 1,
 'response_received_count': 11,
 'scheduler/dequeued': 10,
 'scheduler/dequeued/memory': 10,
 'scheduler/enqueued': 10,
 'scheduler/enqueued/memory': 10,
 'start_time': datetime.datetime(2018, 11, 5, 14, 43, 28, 234828)}
2018-11-05 23:43:43 [scrapy.core.engine] INFO: Spider closed (finished)

リクエストごとに1秒置いて実行していくので少し時間がかかる。 最後にクローラーの統計がダンプされるので、そこで結果のサマリーがわかる。item_scraped_countが一番わかり易い。

結果は items.jlというファイルに書き込まれているが、そのままだと日本語がエスケープされているので、jqを使えば日本語として読める。

 ᐅ cat news.jl | jq .          
{
  "title": "NHK鈴木奈穂子アナ 21日ぶり「ニュース7」復帰「ご心配をお掛けしました」ネット安堵の声",
  "body": " 10月16日から体調不良で休養していたNHK「ニュース7」(月~日曜後7・00)平日キャスターの鈴木奈穂子アナウンサー(36)が5日、21日ぶりに番組に復帰した。冒頭、サブキャスターの高井正智アナウックス)"
}
(以下略)