TwitterがXに改名されて数年が経ちました。現在はtwitter.comへのリンクはx.comへリダイレクトされる仕様になっていますが、この措置がいつまで続くのかは明言されていません。
過去のX社の対応を考えると、ある日に突然twitter.comが無効化されるというアナウンスがなされる可能性もあります。
もしもに備えて、自社サイトにtwitter.comへのリンクが残っているかどうかを、クローラーを作ってチェックしてみます。
本記事に書かれている方法は自分自身が管理しているサイトに対してのみ実施して下さい。
また、自身のサイトに対して行う際にもサイトの負荷を考慮してクローリングの速度には十分に注意して下さい。
完成イメージ
今回作成するクローラーは以下のような動作をします:
- 指定したURLからクロールを開始
- ページ内の全リンクをチェックし、Twitterリンクを検出
- 同一ドメイン内のリンクを再帰的にたどる
- 発見したTwitterリンクをCSVファイルに出力
出力されるCSVファイルの例:
source_url,twitter_url
https://example.com/entry/article1,https://twitter.com/user1
https://example.com/entry/article2,https://twitter.com/user2
環境構築
1. Scrapyのインストール
まずはScrapyをインストールします。
pip install scrapy
インストールが完了したら、バージョンを確認しておきましょう。
scrapy version
# Scrapy 2.13.4
2. プロジェクトの作成
Scrapyのプロジェクトを作成します。
scrapy startproject twitter_link_finder
cd twitter_link_finder
以下のようなディレクトリ構造が生成されます:
twitter_link_finder/
├── scrapy.cfg
└── twitter_link_finder/
├── __init__.py
├── items.py # データ構造の定義
├── middlewares.py # ミドルウェア
├── pipelines.py # データ処理パイプライン
├── settings.py # 設定ファイル
└── spiders/ # スパイダー(クローラー本体)
└── __init__.py
実装
Step 1: Itemの定義
まず、抽出するデータの構造を定義します。items.pyを編集して、Twitterリンクの情報を格納するItemを定義します。
import scrapy
class TwitterLinkItem(scrapy.Item):
source_url = scrapy.Field() # Twitterリンクが見つかったページのURL
twitter_url = scrapy.Field() # 検出したTwitterリンク
シンプルに2つのフィールドを定義しています:
-
source_url: Twitterリンクが掲載されていたページのURL -
twitter_url: 検出したTwitterのURL
Step 2: Spiderの作成
次に、クローラー本体となるSpiderを作成します。spidersディレクトリにtwitter_spider.pyを作成します。
import scrapy
from urllib.parse import urlparse
from twitter_link_finder.items import TwitterLinkItem
class TwitterSpider(scrapy.Spider):
name = "twitter"
allowed_domains = ["techblog.zozo.com"] # クロール対象のドメイン
start_urls = ["https://techblog.zozo.com"] # クロール開始URL
def parse(self, response):
# ページ内の全てのリンクを取得
for link in response.css("a::attr(href)").getall():
if not link:
continue
# Twitterリンクかどうかをチェック
if link.startswith("https://twitter.com") or link.startswith("http://twitter.com"):
item = TwitterLinkItem()
item["source_url"] = response.url
item["twitter_url"] = link
yield item
# 同一ドメイン内のリンクをたどる
parsed = urlparse(link)
if parsed.netloc == "" or parsed.netloc in self.allowed_domains:
yield response.follow(link, callback=self.parse)
コードの解説
クラス属性の設定
name = "twitter"
allowed_domains = ["techblog.zozo.com"]
start_urls = ["https://techblog.zozo.com"]
-
name: スパイダーの識別名。実行時に使用します -
allowed_domains: クロールを許可するドメインのリスト。これ以外のドメインへのリンクは無視されます -
start_urls: クロールを開始するURLのリスト
リンクの抽出
for link in response.css("a::attr(href)").getall():
CSSセレクタを使って、ページ内の全ての<a>タグのhref属性を取得しています。
Twitterリンクの検出
if link.startswith("https://twitter.com") or link.startswith("http://twitter.com"):
item = TwitterLinkItem()
item["source_url"] = response.url
item["twitter_url"] = link
yield item
URLがtwitter.comで始まる場合、Itemを生成してyieldします。
再帰的なクロール
parsed = urlparse(link)
if parsed.netloc == "" or parsed.netloc in self.allowed_domains:
yield response.follow(link, callback=self.parse)
相対URL(netlocが空)または許可されたドメインのリンクの場合、そのリンク先もクロールします。response.follow()を使うことで、相対URLも自動的に解決されます。
Step 3: 設定の調整
settings.pyを編集して、クローラーの動作設定とCSV出力の設定を行います。
BOT_NAME = "twitter_link_finder"
SPIDER_MODULES = ["twitter_link_finder.spiders"]
NEWSPIDER_MODULE = "twitter_link_finder.spiders"
# robots.txtを尊重する
ROBOTSTXT_OBEY = True
# サーバーに負荷をかけないための設定
CONCURRENT_REQUESTS_PER_DOMAIN = 1 # 同時リクエスト数
DOWNLOAD_DELAY = 1 # リクエスト間隔(秒)
# 出力エンコーディング
FEED_EXPORT_ENCODING = "utf-8"
# CSV出力設定
FEEDS = {
"twitter_links.csv": {
"format": "csv",
"fields": ["source_url", "twitter_url"],
"overwrite": True,
},
}
重要な設定項目
ROBOTSTXT_OBEY
ROBOTSTXT_OBEY = True
robots.txtのルールを尊重します。クロールを禁止されているページにはアクセスしません。Webスクレイピングのマナーとして、必ずTrueにしておきましょう。
リクエスト制限
CONCURRENT_REQUESTS_PER_DOMAIN = 1
DOWNLOAD_DELAY = 1
サーバーに負荷をかけないよう、同時リクエスト数を1に制限し、リクエスト間に1秒の間隔を設けています。
CSV出力設定
FEEDS = {
"twitter_links.csv": {
"format": "csv",
"fields": ["source_url", "twitter_url"],
"overwrite": True,
},
}
クロール結果をtwitter_links.csvに出力します。overwrite: Trueを指定することで、実行のたびにファイルを上書きします。
実行
プロジェクトのルートディレクトリで以下のコマンドを実行します:
scrapy crawl twitter
クロールが開始され、ログが出力されます:
2025-12-07 10:00:44 [scrapy.core.engine] INFO: Spider opened
2025-12-07 10:00:46 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://techblog.zozo.com> (referer: None)
...
2025-12-07 10:01:27 [scrapy.core.scraper] DEBUG: Scraped from <200 https://techblog.zozo.com/entry/zozotown-search-personalize>
{'source_url': 'https://techblog.zozo.com/entry/zozotown-search-personalize',
'twitter_url': 'https://twitter.com/dama_yu'}
Twitterリンクが検出されると、上記のようなログが表示されます。
クロールが完了すると、twitter_links.csvが生成されます:
source_url,twitter_url
https://techblog.zozo.com/entry/zozotown-search-personalize,https://twitter.com/dama_yu
https://techblog.zozo.com/entry/iosdc_japan_2017,https://twitter.com/re___you
https://techblog.zozo.com/entry/iosdc_japan_2017,https://twitter.com/shmdevelop
...
カスタマイズ
対象ドメインの変更
別のサイトをクロールする場合は、twitter_spider.pyの以下の部分を変更してください:
allowed_domains = ["example.com"] # クロール対象のドメイン
start_urls = ["https://example.com"] # クロール開始URL
JSON形式で出力する
CSV以外にもJSONやXML形式での出力が可能です。settings.pyを以下のように変更します:
FEEDS = {
"twitter_links.json": {
"format": "json",
"overwrite": True,
},
}
注意点
1. robots.txtの確認
クロール前に対象サイトのrobots.txtを確認しましょう。クロールが禁止されているパスにはアクセスしないようにしてください。
https://example.com/robots.txt
2. 利用規約の確認
サイトの利用規約でスクレイピングが禁止されていないか確認してください。
3. サーバーへの負荷
大量のリクエストはサーバーに負荷をかけます。DOWNLOAD_DELAYを適切に設定し、短時間に大量のリクエストを送らないようにしましょう。
4. クロール時間
サイトの規模によってはクロールに長時間かかる場合があります。DEPTH_LIMITを設定することで、クロールの深さを制限できます:
DEPTH_LIMIT = 3 # リンクを3階層までたどる
5. 重複の除去
同じTwitterリンクが複数のページに掲載されている場合、重複して出力されます。重複を除去したい場合は、Pipelineで処理するか、出力後にCSVを加工してください。
まとめ
Scrapyを使うことで、Webサイト内のTwitterリンクを簡単に検出・収集できるクローラーを作成できました。
今回作成したクローラーのポイント:
- Item: 抽出するデータ構造を定義
- Spider: クロールロジックを実装
- Settings: 動作設定とCSV出力を設定
Scrapyは拡張性が高く、Middlewareを追加することでより複雑な処理も可能です。ぜひ、自分のユースケースに合わせてカスタマイズしてみてください。