概要
Scrapyをstartprojectを使わず1ファイルで記述し、pythonコマンドで実行するサンプルです。
対象サイト( https://blog.qiita.com/ )から各記事のデータを収集してJSON Lines形式でファイル出力します。
実行コマンド 終了はCtrl+Cを2回
$ python main.py
サンプルコード
main.py
# 要インストール
# $ pip install scrapy
import scrapy
from scrapy.crawler import CrawlerProcess
from scrapy.extensions.httpcache import DummyPolicy
class MySpider(scrapy.Spider):
"""スパイダー https://doc-ja-scrapy.readthedocs.io/ja/latest/topics/spiders.html"""
name = "my_spider"
# クロールを開始するURL このURLが最初にリクエストされレスポンスはparseメソッドで処理される
start_urls = ["https://blog.qiita.com/"]
def parse(self, response):
"""一覧ページ処理"""
# 詳細ページへのリンクを選択
for detail_href in response.css('h2.indexArticle_title a::attr("href")'):
# hrefが相対パスの場合は絶対パスに直す
detail_url = response.urljoin(detail_href.get())
# 詳細ページをリクエスト
yield scrapy.Request(
# リクエストするURL
detail_url,
# レスポンスを処理するコールバック
self.parse_detail,
)
# 次ページがあればリクエストする
if next_href := response.css('a.nextpostslink::attr("href")').get():
# Requestオブジェクトを作成するショートカットとしてresponse.followを使用できる
yield response.follow(
# リクエストするURL(相対パスでOK)
next_href,
# レスポンスを処理するコールバック
self.parse,
)
def parse_detail(self, response):
"""詳細ページ処理"""
# アイテムクラスの代わりにdictでもOK
item = dict()
item["title"] = response.css("h1.article_title::text").get()
item["date"] = response.css("time.entry-date::text").get()
item["content"] = response.css("div.article_body").xpath("string()").get()
item["url"] = response.url
# これが1レコード分のデータになる
yield item
class MyItemPipeline:
"""アイテム・パイプライン https://doc-ja-scrapy.readthedocs.io/ja/latest/topics/item-pipeline.html"""
def process_item(self, item, spider):
# contentを省略する
item["content"] = item["content"][:50] + "..."
return item
# 200ステータスのみキャッシュするポリシー
class CachePolicy(DummyPolicy):
def should_cache_response(self, response, request):
return response.status == 200
def get_settings():
# 設定 https://doc-ja-scrapy.readthedocs.io/ja/latest/topics/settings.html
settings = {
# robots.txtポリシーを尊重する
"ROBOTSTXT_OBEY": True,
# ダウンロード間隔 デフォルト0
"DOWNLOAD_DELAY": 1,
# リクエストヘッダーの言語をjaに設定
"DEFAULT_REQUEST_HEADERS": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "ja",
},
# キャッシュを有効にする
"HTTPCACHE_ENABLED": True,
# キャッシュの有効期限 0は期限なし
"HTTPCACHE_EXPIRATION_SECS": 60 * 60 * 24, # 24h
# 200ステータスのみキャッシュする
"HTTPCACHE_POLICY": "__main__.CachePolicy",
# アイテム・パイプラインを設定する
"ITEM_PIPELINES": {"__main__.MyItemPipeline": 500},
# 出力ファイルの設定 https://docs.scrapy.org/en/latest/topics/feed-exports.html#std-setting-FEEDS
"FEEDS": {
"output.jsonl": {
"format": "jsonlines",
"encoding": "utf8",
"overwrite": True,
}
},
# ログレベル デフォルト"DEBUG"
# "LOG_LEVEL": "INFO",
}
return settings
if __name__ == "__main__":
# Scrapy実行
process = CrawlerProcess(settings=get_settings())
process.crawl(MySpider)
process.start()
実行結果
output.jsonl
{"title": "募集ページをリリースします", "date": "2022/6/7", "content": "こんにちは、Qiita運営です。今回は「募集ページ」のリリースについてお知らせします。 募集ページに...", "url": "https://blog.qiita.com/opportunity-release/"}
{"title": "Qiitadonのサービス提供を終了します", "date": "2022/2/1", "content": "こんにちは、Qiita運営です。今日は「Qiitadon」の提供終了についてお知らせします。2017...", "url": "https://blog.qiita.com/terminate-provision-of-qiitadon/"}
{"title": "QiitaのMarkdownパーサーの変更をベータ版として公開します", "date": "2022/2/8", "content": " こんにちは、Qiita運営です。 今日はQiitaのMarkdownパーサーの変更をベータ版として...", "url": "https://blog.qiita.com/replace-markdown-parser-beta/"}
{"title": "QiitaのMarkdownパーサーの変更を正式にリリースします", "date": "2022/2/21", "content": " こんにちは、Qiita運営です。今日はベータ版として公開していたMarkdownパーサーの変更を正...", "url": "https://blog.qiita.com/replace-markdown-parser-general/"}
{"title": "Alexaスキル – Qiita 人気の投稿 の提供を終了します", "date": "2022/3/1", "content": "こんにちは、Qiita運営です。今日は「Alexaスキル – Qiita 人気の投稿」の提供終了につ...", "url": "https://blog.qiita.com/terminate-provision-of-qiita-trend-available-in-alexa-skill/"}
{"title": "記事カードのUI変更をベータ版として公開します", "date": "2022/3/18", "content": "こんにちは、Qiita運営です。今日は、Qiitaの記事カードのUI変更についてお知らせいたします。...", "url": "https://blog.qiita.com/update-article-card-beta/"}
{"title": "期間限定の記事投稿キャンペーン「Qiita Engineer Festa 2022」を6〜7月に開催決定!", "date": "2022/3/25", "content": "こんにちは、Qiita運営スタッフです! 2022年6月1日(水)より「Qiita Engineer...", "url": "https://blog.qiita.com/qiita-engineer-festa-2022-sponsor/"}
...
.scrapyディレクトリ以下にページのキャッシュが保存されます。
$ tree .scrapy
.scrapy
└── httpcache
└── my_spider
├── 02
...