LoginSignup
20
35

More than 5 years have passed since last update.

Python Webスクレイピング実装例

Last updated at Posted at 2017-11-24

前書き

コードをできるだけ書かず、手間のかかるフレームワークやミドルウェアを使わず、手軽さ重視で定期実行に対応した(cronで雑にまわせる)スクレイピングを実装する。
自然言語処理・機械学習の検証のため必要に迫られて実装したもの。似たような要件の方に少しでも役立てば、というところ。

概要

題材は何でもいいのですが、日刊工業新聞Webサイトからトップニュースのタイトルと本文(有料記事等の読めない部分は諦める)を抜き出してsqliteに記録します。
cronで雑に定期実行することを想定して、過去に収集済みの記事は重複して収集しないようにします。
なお、認証要ページや、javascriptで動的生成されるタイプのページには対応しません。

環境

  • Python3
    • BeautifulSoup

必須じゃないですが、Jupyter notebook環境だとDOMまわりのトライアンドエラーが捗ります。

実装

1
import requests
import re
import sqlite3
import hashlib

from time import sleep
from datetime import datetime
from bs4 import BeautifulSoup

domain = "https://www.nikkan.co.jp"
startpath = "/"
basesoup = BeautifulSoup(requests.get(domain + startpath).text, "lxml")

とりえあずhtmlを取ってきてBeautifulSoupに突っ込みます。
しっかりつくるならrequests関連は例外処理を入れておいた方がいいです。タイムアウト等でこけるので。

2
links = []
for a in basesoup.find_all("a", href=re.compile("^/articles/view/[0-9]+")):
    links.append(domain + a.get("href"))
links = list(set(links))

トップページ内のaタグを拾いにいきます。その際にhrefに正規表現で条件を掛け、記事URL以外のaタグを弾きます。

3
texts = []
for link in links:
    sleep(2)
    res = requests.get(link)
    res.encoding = res.apparent_encoding
    soup = BeautifulSoup(res.text, "lxml")
    title = "".join([x.text.strip() for x in soup.find_all("div", class_="ttl")])
    main = "".join([x.text.strip() for x in soup.find("div", class_="txt").find_all("p")])
    text = (title + " " + main).replace("\u3000"," ")
    texts.append(text)

拾ったリンク先URL各々に対してDOM解析し、今度はタイトルと本文を拾いにいきます。
このサイトの場合は、class=ttlのdiv要素にタイトル、class=txtの最初のdiv配下のp要素に本文が入っているので、それで検索を掛けます。
記事によっては複数要素が返ってくるので、joinでまとめて結合します。

4
dbname = "text.db"
dbcon = sqlite3.connect(dbname)
dbcur = dbcon.cursor()

for text in texts:
    insert = "INSERT INTO rawtext(id, source, time, rawtext) VALUES(?, ?, ?, ?)"
    id = hashlib.md5(text.encode("utf-8")).hexdigest()
    source = "nikkan"
    time = datetime.now().isoformat()    
    args = (id, source, time, text)    
    try:
        dbcur.execute(insert, args)
    except sqlite3.Error as e:
        print('sqlite3:', e.args[0])

dbcon.commit()
dbcon.close()

DBに突っ込みます。dbファイルとテーブル定義は事前に作っておいて下さい。
id列はUNIQUE制約を付けておきます。
こうすることで、タイトル+本文のハッシュ値が一致する記事はUNIQUE制約で勝手にはじいてくれるので、雑に定期実行してもデータが重複しません。あまり美しくはない気がしますが。
他のサイトからのデータも放り込むかもしれないのでsource列にデータソース名を入れておきます。あと取得日時も何かと使いそうなのでtime列に保存しておきます。


以上です。上記のスクリプトを収集したいサイト向けに適当に改修してcronで回せば概ね上手く動くんじゃないでしょうか。
今日日ヘッドレスブラウザ必須やろ、という気がしていたのですが、当方要件は堅めのニュースサイトが主だった為か、意外と必要に迫られませんでした。
きっとDBも、Read性能や分散化等を考えると、他に取るべき選択肢があるのでしょうが、中小規模レベルではsqliteの簡単さがやはり魅力です。別途分かち書き要ですがftsで全文検索できたり何気に高機能ですしね。

なお、相手サーバに不可を掛けすぎて怒られても責任は負えませんので、紳士的なクローリングを心がけましょう。

20
35
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
20
35