最初 Tumblr で作っていたウェブサイトを、WordPress に引っ越しさせたのですが、Tumblrにはエクスポート機能が無いので、自前でスクレイピングして WordPress でインポートできる形式の csv ファイルを作ってインポートしました。
ただそれだけの話なのですが、たまには Qiita も更新しておこうと思ったので公開しておきます。
scrapy は、Python 製のスクレイピング用フレームワークです。
今回は、こちらを利用して、Tumblr のウェブサイトから記事のcsvファイルを作ります。
移行に際して、画像も取ってきたいので、記事中に参照している画像もダウンロードして、img タグの中の src 属性を、ダウンロードした画像の名前に変換しています。
完成版のソースコードはgithubで公開しています。
https://github.com/codeforjapan/scrape_c4j_tumblr
まず、scrapy をインストールします。
pip install scrapy
プロジェクトを作ります。
scrapy startproject [プロジェクト名]
以下のようなディレクトリができます。
[プロジェクト名]/
scrapy.cfg # 全体の設定ファイル
[プロジェクト名]/ # プロジェクトの場所
__init__.py
items.py # アイテムの定義を書くファイル
pipelines.py # パイプラインの処理を書くファイル
settings.py # プロジェクトの設定ファイル
spiders/ # スクレイピングの処理を置く場所
__init__.py
まず、プロジェクト配下のsettings.pyに以下の設定を追加します。
#ダウンロード間隔を3秒空ける
DOWNLOAD_DELAY = 3
# エクスポートの文字コードをutf-8に
FEED_EXPORT_ENCODING='utf-8'
# エクスポートフォーマットは csv
FEED_FORMAT='csv'
# 出力するフィールド(items.py に対応)
FEED_EXPORT_FIELDS = ["post_id","post_name","post_author","post_date","post_type","post_status","post_title","post_content","post_category","post_tags","custom_field"]
# イメージダウンロードのための設定
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'scrapy.pipelines.images.ImagesPipeline': 1,
'c4jtumblr.pipelines.C4JtumblrPipeline': 300,
}
# イメージ画像のダウンロード先
IMAGES_STORE = './out/images'
# csv ファイルの出力先
FEED_URI = './out/export.csv'
今回は、spiders 配下に、以下のようなコードを書きました。
# coding: utf-8
from datetime import datetime
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.selector import Selector
from c4jtumblr.items import NewsItem
class C4jSpider(CrawlSpider):
name = 'c4j'
allowed_domains = ['archive.code4japan.org']
start_urls = [
#エントリーポイント(スクレイピングのスタート地点)
'http://archive.code4japan.org/',
]
# 一覧ページのパース処理
def parse(self, response):
# <article> 以下が各記事になっているので、全記事を取得
for article in response.css('article'):
article_page = article.css('.text a::attr("href")').extract_first()
# テキストの記事と、写真記事の場合でマークアップが違うので、失敗していたらもう一つを試す
if article_page == None:
article_page = article.css('.photo-hover a::attr("href")').extract_first()
# response.follow で、指定したURLをフェッチしてparse_newsに渡す
yield response.follow(article_page, self.parse_news)
# 次のページへ
next_page = response.css('#pagination a#older::attr("href")').extract_first()
if next_page is not None:
yield response.follow(next_page, self.parse)
# 個別のページのパース処理
def parse_news(self, response):
# NewsItem クラスには、csv 出力に必要なカラムがフィールドとして入っている
item = NewsItem()
sel = Selector(response)
article = response.css("article")
# 各フィールドの中身を設定。Tumblr に無い情報は固定の文字列にしている。
item['article_id'] = article.css("::attr(id)").extract_first()
item['post_id'] = ""
item['post_date'] = article.css("::attr(date)").extract_first()
item['post_name'] = response.url.split("/")[-1]
item['post_title'] = article.css("div.text h2::text").extract_first()
item['post_author'] = 'hal'
item['post_type'] = 'story'
item['post_status'] = 'draft'
item['post_category'] = ""
item['post_tags'] = " ".join(article.css("a.tag::text").extract())
if item['post_title'] == None:
item['post_title'] = article.css('div.captions p ::text').extract_first()
item['post_content'] = article.css('div.text').extract_first()
if (item['post_content']) == None:
item['post_content'] = article.css('.photo').extract_first() + article.css('div.text-post div.captions').extract_first()
# 画像を発見したら、ダウンロードキューに入れておく
item['image_urls'] = article.css('img::attr("src")').extract()
# 出力
yield item
items.py は以下のようになっています。
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class C4JtumblrItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
class NewsItem(scrapy.Item):
article_id = scrapy.Field()
post_id = scrapy.Field()
post_author = scrapy.Field()
post_name = scrapy.Field()
post_title = scrapy.Field()
post_content = scrapy.Field()
post_date = scrapy.Field()
post_category = scrapy.Field()
post_type = scrapy.Field()
post_status = scrapy.Field()
post_tags = scrapy.Field()
custom_field = scrapy.Field()
image_urls = scrapy.Field()
images = scrapy.Field()
今回、画像を発見したらそれもダウンロードしておくようにしています。
c4j.py の下の方にある、 item['image_urls'] = article.css('img::attr("src")').extract()
の部分ですね。
なんと、scrapy のデフォルトでは、imge_urls プロパティにURLを突っ込んでおくと、勝手にダウンロードをしてくれます。便利ですね。
ただ、ダウンロードした画像のファイル名は、画像のあったURLをSHA1ハッシュ化した名前に変換されます。ローカルでの置き場所も変わるので、記事側の src も変更しなくてはいけません。
その処理を実行するのが、pipeline.py です。
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
class C4JtumblrPipeline(object):
def process_item(self, item, spider):
# replace image src to downloaded image path
for image in item['images']:
item['post_content'] = item['post_content'].replace(image['url'], '/images/' + image['path'])
return item
あとは、ディレクトリに移動してコマンドを叩くだけです。
% cd scrape_c4j_tumblr #プロジェクト名のディレクトリ
% scrapy crawl c4j
これを実行すると、out/export.csv が完成します。画像ファイルは out/images/full 以下に保存されていきます。
あとは、できあがったcsvファイルをWordPressに食わせて、画像ファイルをしかるべき場所に配置すればOKでした。やりましたね!
僕としては、画像パスの書き換え部分がとても便利でした!
それでは!