2026年11月6日、Pythonでたくさんのページを自動で巡回するためのフレームワークScrapyの2.20.0が公開されました。9月の2.19.0から、およそ2か月ぶりの新しい版です。
今回の目玉の1つは、コールバックの中で、リクエストをawaitして応答を待てるようになったことです。別のページの情報を組み合わせる処理が、ずっと素直に書けるようになりました。
もう1つの大きな柱が、セキュリティの修正です。巡回先のサイトに悪意があったとき、手元のファイルを読まされたり、認証情報を盗まれたりするおそれのある問題が、まとめて直されています。
まずはScrapyとはどんなものか¶
最初に、Scrapyを知らない方のために、どんなフレームワークなのかを簡単に紹介します。Scrapyは、Webページを次々にたどりながら、必要な情報を取り出すクローラーを作るためのフレームワークです。
1ページだけ取ってくるなら、requestsとBeautifulSoupの組み合わせで十分です。Scrapyは、何百、何千というページを、並行して効率よく巡回したいときに力を発揮します。
| 道具 | 得意なこと |
|---|---|
| requests | 1つのページを取ってくる |
| BeautifulSoup | 取ってきたHTMLから、情報を取り出す |
| Scrapy | たくさんのページを並行して巡回し、取り出して保存するまでをまとめて行う |
スクレイピングの基本は、こちらで紹介しています。【関連記事】PythonのBeautifulSoupとは?
スパイダーとコールバック¶
Scrapyでは、巡回のしかたを書いたクラスをスパイダーと呼びます。ページを取ってくると、そのページを受け取って処理する関数、つまりコールバックが呼ばれます。
これまでは、別のページの情報も必要なときに、新しいリクエストを作り、その結果を受け取る別のコールバックを用意する必要がありました。処理が複数の関数にまたがるので、初心者には流れを追いにくい書き方でした。
2.20で何が変わったのかを整理しよう¶
続いて、2.20.0の主な変更を表にまとめます。内容は、Scrapyの公式リポジトリにある変更履歴news.rstにもとづいています。
| 分類 | 主な変更 |
|---|---|
| 新機能 | コールバックの中で、リクエストをawaitして応答を待てる |
| 新機能 | 文字コードや基準のURLを、ブラウザに近い方法で判定 |
| セキュリティ | 応答が使うメモリの量に、既定で上限を設定 |
| セキュリティ | file://やftp://のリンクを、既定ではたどらない |
| セキュリティ | 認証情報を、別のサイトへのリダイレクト先に送らない |
| 対応範囲 | Python 3.10とPyPyのサポートを終了 |
依存するライブラリも変わりました。変更履歴によると、文字コードなどの判定を担うfrostworkや、aiodns、aioftpなどが新しく加わり、これまで使われていたcharset-normalizerは不要になっています。
PyPIの記録によると、2.20.0が必要とするPythonは3.11以上です。主要なライブラリが3.10を外していく流れは、こちらでもまとめました。【関連記事】Python 3.10のサポート終了から2週間、主要ライブラリの対応状況
リクエストをawaitで待てるようになった¶
まずは、新機能から見ていきましょう。公式ドキュメントのコルーチンの章には、2.20.0で、Requestのオブジェクトをawaitできるようになったと書かれています。
商品名と値段を、別々のページから集める¶
実際に試してみましょう。手元で小さなWebサーバーを立て、商品名だけが書かれたページと、値段だけが書かれたページを用意しました。
from scrapy import Request, Spider
class ProductSpider(Spider):
name = "product"
start_urls = ["http://127.0.0.1:8799/product.html"]
async def parse(self, response):
# 値段は別のページにあるので、その場でリクエストして応答を待つ
price_response = await Request("http://127.0.0.1:8799/price.html")
yield {
"name": response.css("h1::text").get(),
"price": price_response.css("#price::text").get(),
}
このスパイダーを、2.19.0と2.20.0のそれぞれの仮想環境で動かし、集めた結果をJSONに書き出しました。手元のPython 3.11で実行した結果です。
Scrapy 2.19.0
TypeError: object Request can't be used in 'await' expression
(結果は空)
Scrapy 2.20.0
[{"name": "りんごジュース", "price": "320円"}]
2.19.0では、Requestはawaitできないというエラーになりました。2.20.0では、1つのコールバックの中で、2つのページの情報を組み合わせて取り出せています。
使うときの注意¶
ドキュメントによると、awaitしたリクエストには、ダウンローダーミドルウェアと呼ばれる仕組みだけが適用されます。また、callbackやerrbackを設定したリクエストは、awaitできないと書かれています。
asyncとawaitの基本については、こちらで紹介しています。【関連記事】Pythonのasyncioとは?
file://のリンクを、勝手にたどらなくなった¶
ここからは、セキュリティの修正です。変更履歴によると、リンクを抜き出すLinkExtractorとSitemapSpiderが、既定ではhttp://とhttps://のURLだけをたどるようになりました。
これまでは、file://やftp://のリンクもたどっていました。悪意のあるサイトが、file:///etc/passwdのようなリンクを置いておけば、手元のファイルを読ませることができたわけです。
2.19.0と2.20.0で比べてみた¶
実際に、ふつうのリンクに混ぜて、file://とftp://のリンクを置いたページを用意し、LinkExtractorで抜き出してみました。
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
html = """
<a href="/about">会社概要</a>
<a href="https://example.org/news">ニュース</a>
<a href="file:///etc/passwd">ローカルのファイル</a>
<a href="ftp://ftp.example.net/data.csv">FTPのファイル</a>
""".encode()
response = HtmlResponse(url="https://example.com/", body=html, encoding="utf-8")
for link in LinkExtractor().extract_links(response):
print(link.url)
実行した結果です。
Scrapy 2.19.0
https://example.com/about
https://example.org/news
file:///etc/passwd
ftp://ftp.example.net/data.csv
Scrapy 2.20.0
https://example.com/about
https://example.org/news
2.19.0では、ローカルのファイルとFTPのファイルへのリンクも、たどる候補に入っていました。2.20.0では、ふつうのWebページへのリンクだけが残っています。
変更履歴によると、ほかの方式もたどりたい場合は、LinkExtractorの新しいschemes引数で指定できます。
たどる方式を自分で選ぶ¶
試しに、先ほどのコードをLinkExtractor(schemes=["https", "ftp"])に変えて、2.20.0で実行してみました。すると、2つのWebページに加えて、ftp://ftp.example.net/data.csvも抜き出されました。
FTPのファイルも集めたい、といった事情がある場合だけ、このように必要な方式を明示して許可するのが安全です。何も指定しなければ、ふつうのWebページだけをたどる、という既定の動きは、初心者にとって心強い守りになります。
そのほかのセキュリティ修正¶
変更履歴には、ほかにも多くのセキュリティ修正が並んでいます。代表的なものを表にまとめます。
| 修正 | 中身 |
|---|---|
| 応答のメモリ | 応答が使うメモリの量に、既定で上限を設定。新しい設定RESPONSE_MAX_ACTIVE_SIZEで変えられる |
| 圧縮の重ねがけ | gzipを何千回も重ねた応答で、CPUとメモリを使い果たす問題。既定では1回分だけ解凍する |
| 認証情報 | http_userとhttp_passを、最初に使ったサイト以外へのリダイレクト先に送らない |
| プロキシ | リダイレクトのときも、no_proxyの設定を守る |
| S3への保存 | 既定の設定で、HTTPSを使うように |
認証情報が盗まれるおそれ¶
3つ目の修正を、もう少し詳しく見ておきましょう。変更履歴によると、これまでは、ログインに使う認証情報が、別のサイトへのリダイレクトのあとにも送られていました。
そのため、巡回先のサイトが、自分の管理するサーバーへリダイレクトさせるだけで、認証情報を盗めるおそれがあったそうです。2.20.0では、最初に使ったサイトにだけ送るように直されました。
ログインが必要なサイトを巡回している方は、早めに上げておきましょう。HTTP通信の基本は、こちらで紹介しています。【関連記事】Pythonのrequestsとは?
上げる前にやっておきたいこと¶
最後に、Scrapyを2.20.0へ上げる前に確かめておきたいことを整理します。
| 順番 | 確認すること |
|---|---|
| 1 | Python 3.11以上を使っているか。PyPyで動かしていないか |
| 2 | file://やftp://のリンクをたどる前提のスパイダーがないか |
| 3 | 大きなファイルを扱うなら、RESPONSE_MAX_ACTIVE_SIZEの上限に当たらないか |
| 4 | Scrapy shellのfetch()を使った手順が、変更後の動きでも通るか |
変更履歴によると、Scrapy shellのfetch()は、スケジューラーを通さずに直接ダウンロードするようになりました。そのため、重複したリクエストを除く仕組みなどは、fetch()には適用されなくなっています。
私は10年ほどエンジニアとして開発に関わってきましたが、取引先のサイトを巡回するクローラーで、リダイレクトの先が想定外のドメインになっていて冷や汗をかいたことがあります。巡回先は自分で選んでいても、その先のリンクやリダイレクトまでは選べないことを、いつも意識しておきたいところです。
まとめ¶
2026年11月6日、Scrapy 2.20.0が公開されました。コールバックの中でリクエストをawaitして応答を待てるようになり、file://のリンクを勝手にたどる問題や、認証情報をリダイレクト先に送る問題など、多くのセキュリティ修正も入っています。
クローラーは、知らないサイトのデータを受け取る道具です。安全面の修正が多い今回は、使っている方はできるだけ早く上げておきましょう。
ここまでお読みいただきありがとうございました。