前書き
コードをできるだけ書かず、手間のかかるフレームワークやミドルウェアを使わず、手軽さ重視で定期実行に対応した(cronで雑にまわせる)スクレイピングを実装する。
自然言語処理・機械学習の検証のため必要に迫られて実装したもの。似たような要件の方に少しでも役立てば、というところ。
概要
題材は何でもいいのですが、日刊工業新聞Webサイトからトップニュースのタイトルと本文(有料記事等の読めない部分は諦める)を抜き出してsqliteに記録します。
cronで雑に定期実行することを想定して、過去に収集済みの記事は重複して収集しないようにします。
なお、認証要ページや、javascriptで動的生成されるタイプのページには対応しません。
環境
- Python3
- BeautifulSoup
必須じゃないですが、Jupyter notebook環境だとDOMまわりのトライアンドエラーが捗ります。
実装
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関連は例外処理を入れておいた方がいいです。タイムアウト等でこけるので。
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タグを弾きます。
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でまとめて結合します。
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で全文検索できたり何気に高機能ですしね。
なお、相手サーバに不可を掛けすぎて怒られても責任は負えませんので、紳士的なクローリングを心がけましょう。