イントロ
ウェブページの情報を、ささっと、1時間ぐらいで簡単に集めたいなどという場合、マウスクリックの鬼となりブラウザからコピペをするのもメンドクサイし、今後当該ウェブページをもう一度訪れる可能性もほとんど無いから わざわざスクリプトを書くモチベーションはゼロ、ああメンドクセェナァ…。
と、このような場面でいかに手を抜くかが今回のテーマです。
1 超手抜き編
1.1 スープの仕込み
まずはスープの仕込み。これはお馴染みのスクリプト。
元の HTML に含まれる head
の部分は今回のようにテキストだけを抽出したいときには普通は要らないので、あらかじめ soup = soup.body
として HTML の body 部分だけを取り出しておきます(この1行は無くても差し支えない)。
#!/usr/bin/env python3 import requests import pandas as pd from bs4 import BeautifulSoup # Test page: Wikipedia (lang=ja) 「ウェブスクレイピング」のページ; my_url = 'https://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%A7%E3%83%96%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0' res = requests.get(my_url) res.encoding = res.apparent_encoding soup = BeautifulSoup(res.text, 'lxml') soup = soup.body # Remove 'head'
1.2 スクレイピング:欲しい要素の抽出
本来ならばここからが本番、 find
や select
を駆使して必要な情報を華麗に抽出、の場面ですが、「ささっと、1時間」要件があるので、手抜きを教義とする我が Cult Occult Coding™ としては、「なんでもいいから文字っぽいものは全部抽出する、構造とか semantics とか知らんがな」なスタンスで臨みます。
で、文字っぽいものを抽出するお手軽メソッドは👇こんな感じ。それぞれの Option を使った場合に実際にどうなるか興味のある方には実験してもらうとして説明も手抜き、今回は Option 1 で行きます。
# Option 1: soup.find_all(text=True) # Option 2: soup.findAll(text=True) # Option 3: soup.select('*', text=True)
1.3 リスト/表に整理
1.2 の Option 1 で得られた soup.find_all
を見てみると、抽出されたテキストがリストの形でどわーっと出てきて(見本省略)何がなんだかわからないので、tag_name
と elm_text
(テキスト本体)に分けて list_elements
というリストに 整理して状況を観察できるようにしておきます。
list_elements = [] for elm in soup.find_all(text=True): tag_name = elm.parent.name elm_text = elm.text.strip() list_elements.append([tag_name, elm_text])
1.4 Excel ファイルに保存、あとはどうとでも。
結果は、お手軽に Pandas
を利用して Excel File に書き出します。
df = pd.DataFrame(list_elements, columns=['tag_name', 'inner_html']) df.to_excel('foobar.xlsx', index=False)
いまテストページ my_url
で試したところ、300 行程度のテーブルができましたが、head
部分の残骸とかいろいろ不必要なものが含まれています(2022-09-03に実験)。
そこで、この辺は Excel の機能を使って tag_name
でフィルタリングするなりソートするなりして目的の情報を抽出すれば良し、ということになります。ああ、楽ちん、楽ちん。
おしまい。🍻
2 ちょっとひと手間編
2.1 タグの状況を調べる
超手抜き編では文字っぽいものを手当たり次第に全部抽出したので、そのままではワケがわからない表ができました。そしてこれを Excel の機能を使って掃除していくという怠惰な戦略であったことであることよ(詠嘆)。
そんなの戦略じゃ無い、もっと真面目にやれ!ということならば、んじゃひと手間加えることにして、まずはどんな html tag が使われているのかを調べてみます。
list_tags = [elm.parent.name for elm in soup.find_all(text=True)] set_tags = set(list_tags) # Remove duplicates. print(set_tags) # Results: { 'code', 'span', 'nav', 'body', 'header', 'html', 'tr', '[document]', 'h1', 'a', 'div', 'form', 'li', 'b', 'pre', 'footer', 'style', 'ol', 'main', 'ul', 'head', 'button', 'title', 'script', 'td', 'p', 'label' }
2.2 注目した tag からテキストを抽出
これを見て、必要な tag
を拾い出せば良いでしょう。
どの tag
を抽出するかはその時の状況次第ですが、どのような場合でも大体必要になりそうなものは、 h1
, h2
などの見出しタグ、段落の p
タグ、部分的に文字装飾をつけるときなどに使う span タグ、強調の b
タグ、リンクを貼る時に使うお馴染みの a
タグあたりでしょうか [1]。
あと、li
などの リスト系も入れておいた方が良さそうです。
他方、td
などの表組み(table)系はどうしようかな、と。表をきれいに抽出したいのなら DataFrame.read_html
を使った方が遥かに楽ですが、とりあえず target に入れておきますか(要らなきゃ Excel 上で削ればいいんだし)。
こんな感じで TARGET_TAGS
を決めてテキストを抽出。
ファイル保存の部分はさきほどのまったく同じです(再掲)。
TARGET_TAGS = {'code', 'span', 'h1', 'a', 'li', 'b', 'pre', 'td', 'p'} list_elements = [] for elm in soup.find_all(text=True): tag_name = elm.parent.name elm_text = elm.text.strip() if not tag_name in TARGET_TAGS: # Filter for the Targets continue list_elements.append([tag_name, elm_text])
df = pd.DataFrame(list_elements, columns=['tag_name', 'inner_html']) df.to_excel('foobar.xlsx', index=False)
2.3 全部乗せマシマシ版(参考)
なお、テキストを持ってそうなタグ候補を全部リストすると以下のようになります[1]。
もっとも、 オールマイティーを狙って過度に一般化してしまうと「超手抜き編」とあまり変わらなくなっちゃうし、何かの加減でエラーの原因になったりもするので、そのあたりは適当なさじ加減で。
MASHIMASHI_TAGS = [ 'title', # Document metadata 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', # Content sectioning # Text content 'blockquote', 'dd', 'dt', 'figcaption', 'li', 'p', 'pre', # Inline text semantics 'a', 'abbr', 'b', 'cite', 'code', 'data', 'em', 'mark', 'q', 's', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'time', 'var', # Table content 'caption', 'td', 'th']
Enjoy!
References
[1] MDN. HTML 要素リファレンス