LoginSignup
4
3

More than 5 years have passed since last update.

Scrapy を使ってTumblrの記事をエクスポートしてWordPressにインポートする

Last updated at Posted at 2017-12-15

最初 Tumblr で作っていたウェブサイトを、WordPress に引っ越しさせたのですが、Tumblrにはエクスポート機能が無いので、自前でスクレイピングして WordPress でインポートできる形式の csv ファイルを作ってインポートしました。

ただそれだけの話なのですが、たまには Qiita も更新しておこうと思ったので公開しておきます。

scrapy は、Python 製のスクレイピング用フレームワークです。

今回は、こちらを利用して、Tumblr のウェブサイトから記事のcsvファイルを作ります。

Kobito.jvOfAP.png
(こちらが引越し元のサイト)

移行に際して、画像も取ってきたいので、記事中に参照している画像もダウンロードして、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 配下に、以下のようなコードを書きました。

spiders/c4j.py
# 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 は以下のようになっています。

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 です。

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でした。やりましたね!
僕としては、画像パスの書き換え部分がとても便利でした!
それでは!

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3