もなかアイスの試食品

「とりあえずやってみたい」そんな気持ちが先走りすぎて挫折が多い私のメモ書きみたいなものです.

Python+Scrapyでスクレイピングした結果をDB(PostgreSQL)に保存する

はじめに

前に、Scrapyでクローニング・スクレイピングするスクリプトを作成した。

このときは、とあるディレクトリにスクレイピングした結果をjsonにして出力した。

↓そのときの話

monakaice88.hatenablog.com

そして、出力したjsonを別のスクリプトでDBに保存しようかと考えていた。

前回全く使用していなかった「pipelines.py」に処理を追加すると、DBに追加出来ることがわかったので、処理を追加してみた。

目次

参考サイト

doc.scrapy.org

環境

  • PostgreSQL 11
  • Python 3.6.8
  • Scrapy 1.5.1
  • psycopg2
  • Scrapyで作成したプロジェクト名:MachiMachi

DBのテーブル作成

スクレイピングした結果を保存するテーブルを作成する。

今回は、以下のデータを登録するようにした。

  • URL【主キー】
  • ドメイン
  • 登録日時
  • タイトル
  • コンテンツ(本文)

SQLはこんな感じ

-- コンテンツ
create table contents (
  url text not null
  , domain_name text not null
  , register_date timestamp with time zone not null
  , title text not null
  , contents text not null
  , constraint contents_PKC primary key (url)
) ;

comment on table contents is 'コンテンツ';
comment on column contents.url is 'URL';
comment on column contents.domain_name is 'ドメイン';
comment on column contents.register_date is '登録日時';
comment on column contents.title is 'タイトル';
comment on column contents.contents is 'コンテンツ';

スパイダーの作成

今回も前回と同様に、「ライフハッカー(日本語版)」と「TechCrunch(日本語版)」をクローニングする。

前に作成したスパイダーと全く同じものを使うので、今回は省略。

DB接続用の設定ファイルを作成

Scrapyで作成したプロジェクトディレクトリにDB接続用の設定ファイル「setting.ini」を作成し、pipelineクラスから使用するようにした

[default]
host=192.168.xx.xx
port=5432
dbname=xxxxxxx
user=xxxxxxx
password=xxxxxxx

パイプの作成

Scrapyのドキュメントを参考に、スクレイピングした結果をDBにインサートする処理を作成

URLを主キーにしているので、既に同じURLが存在している場合は、インサートしないようにしている。

from configparser import ConfigParser
import logging
import psycopg2
import datetime


class MachimachiPipeline(object):

    def __init__(self):
        self.connection = None
        self.cursor = None
        self.register_datetime = datetime.datetime.now()

    def open_spider(self, spider):
        """
        スパイダーが実行されたときに呼ばれる
        :param spider:実行中のスパイダー
        """
        config = ConfigParser()
        config.read('setting.ini')
        host = config.get('default', 'host')
        port = config.get('default', 'port')
        db_name = config.get('default', 'dbname')
        user = config.get('default', 'user')
        password = config.get('default', 'password')

        logging.info('connecting to db.')
        self.connection = psycopg2.connect('host=%s port=%s dbname=%s user=%s password=%s'
                                           % (host, port, db_name, user, password))
        self.connection.autocommit = False
        self.cursor = self.connection.cursor()
        logging.info('connected to db.')

    def close_spider(self, spider):
        """
        スパイダーの処理が終わるときに呼ばれる
        :param spider:処理中のスパイダー
        """
        self.connection.commit()
        self.cursor.close()
        self.connection.close()
        logging.info('close connection to db.')

    def process_item(self, item, spider):
        """
        スクレイピングされた項目毎に呼ばれる
        :param item:スクレイピングで取得したデータ
        :param spider:処理中のスパイダー
        :return:dictデータ
        """
        url = item['url']
        self.cursor.execute('SELECT * FROM contents WHERE (url = %s)', (url,))
        record = self.cursor.fetchone()
        if record is not None:
            logging.info('url is already registered. url:%s' % (url,))
            return item
        else:
            values = (
                item['url'],
                spider.allowed_domains[0],
                self.register_datetime,
                item['title'],
                item['body']
            )
            self.cursor.execute('INSERT INTO contents (url, domain_name, register_date , title, contents)'
                                ' VALUES (%s, %s, %s, %s, %s)', values)
            logging.info('contents is registered!!. url:%s' % (url,))
            return item

設定の変更

Scrapyの設定も変更する。

「settings.py」の中に、パイプラインの設定がコメントアウトされているので、そのコメントアウトを解除する。

# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'MachiMachi.pipelines.MachimachiPipeline': 300,
}

クローニングの実行

全スパイダーのクローニングを実行するスクリプトを作成する(前に作成したものを一部修正しただけ)

修正点としては、DBに保存するのでファイル出力をしないようにした。

import subprocess
import multiprocessing


def get_crawler_list():
    process = subprocess.Popen('scrapy list', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout_data, stderr_data = process.communicate()
    if process.returncode == 0:
        strings = stdout_data.decode('utf-8').split('\n')
        return list(filter(None, strings))
    else:
        raise RuntimeError()


def execute_scraping(crawler_name):
    cmd = 'scrapy crawl %s --loglevel=INFO' % (crawler_name,)
    subprocess.call(cmd.split())


def main():
    jobs = []
    for crawler_name in get_crawler_list():
        job = multiprocessing.Process(target=execute_scraping, args=(crawler_name,))
        jobs.append(job)
        job.start()

    [job.join() for job in jobs]

    print('finish !!!!')


if __name__ == '__main__':
    main()

実行結果

クローニングを実行すると、PostgreSQLのテーブルに、以下のようにデータが保存された。(見やすくするため、ドメインとタイトルのみ表示)

select domain_name, title from contents;
    domain_name    |                                                                       title                                                                       
-------------------+-------------------------------------------------------------------------------------------------------------
 www.lifehacker.jp | 孫の世話をする祖父母は長生きする:調査結果
 jp.techcrunch.com | Amazonが家庭用メッシュルーターのEeroを買収してEcho製品拡販のベースに
 jp.techcrunch.com | ジェフ・ベゾスのメッセージ暴露、サウジが関与を否定
 jp.techcrunch.com | 米国のiPhoneユーザーが昨年アプリに使った金額は平均79ドル、前年比36%アップ
 jp.techcrunch.com | GoogleドキュメントのAPIでタスクの自動化が可能に
 jp.techcrunch.com | 無登録物件のリスト掲載でパリ市がAirbnbを告訴
 jp.techcrunch.com | 小売・飲食企業のアプリ開発支援を手がけるエンターモーションが2億円調達
 jp.techcrunch.com | ソフトバンク、自動運転配達のnuroに9.4億ドルを資金提供
 www.lifehacker.jp | 合体と分離ができる3in1トラベルバッグ「JW Weekender」を使ってみた
 www.lifehacker.jp | そこまでの生産性、本当に必要ですか?
 www.lifehacker.jp | キッチンに貼るだけで鍋蓋もまな板もスッキリ収納。菜箸置きにも使えそう〜
 www.lifehacker.jp | 「この1本」で仕事はもっと上手くいく。ビジネスコーチに教わる缶コーヒー活用術
 www.lifehacker.jp | オフィスとジムで兼用できる! どんなシーンでも履ける万能シューズがコール ハーンから登場
 www.lifehacker.jp | 贈った方も得をする! コスパ抜群な「バレンタインギフト」を選ぶコツとは?
 www.lifehacker.jp | 元Google人材育成統括部長からのメッセージ「もうがんばらないでください」
 www.lifehacker.jp | 子どもに初めてのスマホ、何に注意すべき? 「2019年度版チェックリスト」
 www.lifehacker.jp | 世界的メール配信サービス会社メールチンプのCEO、ベン・チェストナットさんの仕事術
 www.lifehacker.jp | 働き方を変えるには「短パン」から? 全員「複業」スタートアップCEOの仕事術
 www.lifehacker.jp | オフシーズンの劇場をレンタルスペース化。アーティスティック・ディレクター、エイドリアンさんの仕事術
 www.lifehacker.jp | 親が子どもに言ってはいけない8つの言葉とは?
 www.lifehacker.jp | 自分が「敏感すぎる」「繊細すぎる」人かどうかを測る「HSPセルフチェック」

おわりに

パイプラインを使い、DBに登録する処理ができた。

ドキュメントには、パイプラインの使用用途として、以下を例に挙げていた

  • HTMLのクレンジング
  • データのバリデーション
  • 重複データの削除
  • データベースへの保存

Scrapyでクローニング・スクレイピングした結果を、他のモジュールで加工・解析・保存をやろうと考えていたけど、パイプラインの組み合わせだけで、テキストマイニングまで出来そう。