山傘のプログラミング勉強日記

プログラミングに関する日記とどうでもよい雑記からなるブログです。

Pythonクローリング&スクレイピング[増補改訂版] ―データ収集・解析のための実践開発ガイドー その7

第3章 ライブラリによる高度なクローロング・スクレイピング

今回で3章は終わりです。電子書籍

MongoDB に保存

スクレイピングしたものを MongoDB に保存します。

import lxml.html
import requests
from pymongo import MongoClient

client = MongoClient('localhost', 27017)
db = client.scraping
collection = db.books

collection.delete_many({})

tmp = requests.get("https://gihyo.jp/dp")
html = lxml.html.fromstring(tmp.text)

# href 要素の URL を絶対 URL にする
html.make_links_absolute('https://gihyo.jp/')

for a in html.cssselect('#listBook > li > a[itemprop="url"]'):
  url = a.get('href')
  p = a.cssselect('p[itemprop="name"]')[0]
  title = p.text_content()
  collection.insert_one({'url': url, 'title': title})

for link in collection.find().sort('_id'):
  print(link['_id'], link['url'], link['title'])

電子書籍の情報を MongoDB に保存する

from typing import Iterator
import requests
import lxml.html
import time
import re
from pymongo import MongoClient


def main():
  """
  クローラーのメイン処理
  """
  # MongoDB に保存する
  client = MongoClient('localhost', 27017)
  collection = client.scraping.ebooks # scrapingデータベースからebooksを得る
  # データを一意に識別するキーを格納するkeyフィールドにユニークなインデックスを作成する
  collection.create_index('key', unique=True)

  session = requests.Session() # 複数ページのクロールで負荷を軽減できる
  response = session.get('https://gihyo.jp/dp')
  urls = scrape_list_page(response)
  
  for url in urls:
    key_book = extract_key(url)
    ebook = collection.find_one({'key': key_book})
    if not ebook: # データが存在しない場合、詳細ページにアクセスしてデータを取得する
      time.sleep(0.5) # 0.5秒ごとにアクセスする
      response_detail = session.get(url)
      ebook = scrape_detail_page(response_detail)
      collection.insert_one(ebook)

    for key, value in ebook.items():
      print(key, value)
    
  
def scrape_list_page(response: requests.Response) -> Iterator[str]:
  """
  Response から URL を抜き出す
  """
  html = lxml.html.fromstring(response.text)
  html.make_links_absolute(response.url)
  for a in html.cssselect('#listBook > li > a[itemprop="url"]'):
    url = a.get('href')
    yield url


def scrape_detail_page(response: requests.Response) -> dict:
  """
  詳細ページのRequestから電子書籍の情報を取得する
  """
  html = lxml.html.fromstring(response.text)
  ebook = {
    'url': response.url,
    'key': extract_key(response.url),
    'title': html.cssselect('#bookTitle')[0].text_content(),
    'price': html.cssselect('.buy')[0].text.strip(), # 空白を取り除く
    # リスト内包表記
    'content': [normalize_spaces(h3.text_content()) for h3 in html.cssselect('#content > h3')],
  }
  return ebook

def normalize_spaces(s: str) -> str:
  """
  連続する空白を一つの空白に置き換え、前後の空白を削除して新しい文字列を取得する
  """
  # raw文字列として扱う
  return re.sub(r'\s+', ' ', s).strip()


def extract_key(url: str) -> str:
  """
  URL からキーを抜き出す (ISBN)
  """
  match = re.search(r'/([^/]+)$', url)
  return match.group(1)


if __name__ == '__main__':
  main()

感想

HTML ファイルをスクレイピングして、ハイパーリンクをたどることで電子書籍の情報を取得し、MongoDB に保存するというクローラーを作成しました。

スクレイピングしたデータを何に使うかも同時に考えていきたいですね。