初めてBeautifulSoupでWebスクレイピングして得たTips、ハマったこと
新人研修ぶりにコード書いた
Tips
公式ドキュメントを読む
Beautiful Soup Documentation — Beautiful Soup 4.4.0 documentation
近藤茂徳さんという神がほぼ完全に訳してくれています。
kondou.com - Beautiful Soup 4.2.0 Doc. 日本語訳 (2013-11-19最終更新)
GoogleSpreadSheet連携便利
おまじないの部分だけ書いときます。
from google.colab import auth from oauth2client.client import GoogleCredentials import gspread # 認証処理 auth.authenticate_user() gc = gspread.authorize(GoogleCredentials.get_application_default()) # https://docs.google.com/spreadsheets/d/シートid/edit#gid=0 sh = gc.open_by_key('シートid') # 先頭のシートを開く worksheet = sh.get_worksheet(0)
要素の塊でとると楽っぽい
soup.find("table tr:nth-of-type(2) td:nth-of-type(1)") soup.find("table tr:nth-of-type(3) td:nth-of-type(1)") soup.find("table tr:nth-of-type(4) td:nth-of-type(1)")
こんな感じでやってたけど、どう考えてもtable
で取ってきた後に相対的にtd
取った方が楽に運用できる。
上記の例だとhtml変わるとめんどい。余計な変数も減る。
soup_table = soup.find('table') soup_table.find("tr:nth-of-type(2) td:nth-of-type(1)") soup_table.find("tr:nth-of-type(3) td:nth-of-type(1)") soup_table.find("tr:nth-of-type(4) td:nth-of-type(1)")
find_all()はリストを、find()はbs4.element.Tagを返す
print(type(soup.find_all('td'))) print(type(soup.select('td'))) print(type(soup.find('td'))) print(type(soup.select_one('td'))) # <class 'bs4.element.ResultSet'> # <class 'list'> # <class 'bs4.element.Tag'> # <class 'bs4.element.Tag'>
と思ったら違ったわ。bs4.element.ResultSet!?誰だお前!!!!!!
1個取りたいならfind()かselect_one()
print(html.select("table tr:nth-of-type(2) td:nth-of-type(1)")) # ['解放しろ'] print(html.select_one("table tr:nth-of-type(2) td:nth-of-type(1)")) # 解放しろ
td:nth-of-type(1)
は1個しかないので、select()
よりselect_one()
の方が扱いやすい。
リストイラナイ
bs4.element.ResultSet!?!?!誰だお前!?!?!!?!?!
タグの表記揺れはない
BeautifulSoupで持ってきたhtmlのタグは最適化されるっぽい。
html="解放<br>しろ、<br/>全て<br />を。" print(BeautifulSoup(html, 'lxml')) # <html><body><p>解放<br/>しろ、<br/>全て<br/>を。</p></body></html>
<br>
, <br/>
, <br />
が全部<br/>
になってるね。あとhtmlとbodyとpも自動的に付加されてる。
例えば、brタグの削除に複数条件指定しなくてもいい。
#正規表現モジュールreを用いたタグ除去 re.split("<br/>|<br />", "")
こんな風にしてたけど消すのめんどいからそのままにしてるはw
変数同時宣言
初期化に便利。
elm1 = "" elm2 = "" elm1 = elm2 = ""
ただし、これは後にハマる。
x, y, z = 1, 2, 3 print(x, y, z) # 1 2 3
1行で書けるこれも楽。
リスト作るならリスト内包表記が便利説
x, y, z = 1, 2, 3 int_list= [] for i in [x, y, z]: int_list.append(i) print(int_list) # [1, 2, 3] print([i for i in [x, y, z]]) # [1, 2, 3]
リストがいっぱいあると初期化がめんどくなる。
リスト内包表記なら3行が1行に。
if文を1行にする: 三項演算子
条件式が真の時の処理 if 条件式 else 条件式が偽の時の処理
下記x > 1
ならy = "解☆放"
、それ以外ならy = "未解放…"
x = 2 y = "解☆放" if x > 1 else "未解放…" print(y) # 解☆放
▲と▼は等価。
x = 2 if x > 1: y = "解☆放" else: y = "未解放…" print(y) # 解☆放
三項演算子の中に三項演算子を使えばelifも書ける。
x = 0 y = "解☆放" if x > 1 else "わたしは1です" if x == 1 else "未解放…" print(y) # 未解放…
上述のリスト内包表記と併用すると便利。
要素のテキストを取得する: text, string
BeautifulSoupではgetText()
が用意されてるけど、下記でもテキストだけ取得できる。
text1 = BeautifulSoup('<p>解放しろ、全てを</p>') text2 = BeautifulSoup('<p><b>解放しろ</b>、全てを</p>') print(text1.text) print(text1.string) print(text2.text) print(text2.string) # 解放しろ、全てを。 # 解放しろ、全てを。 # 解放しろ、全てを。 # None
string
だと子要素が複数ある時にNone
が返されるので注意。
改行コード削除: replace(), splitlines()
html = ''' 解放しろ、 全てを。 ''' soup = BeautifulSoup(html, 'lxml') print(soup.text) # 解放しろ、 # 全てを。 print(soup.text.replace("\n", "")) # 解放しろ、全てを。 print(soup.text.splitlines()) # ['解放しろ、', '全てを。'] print(soup.text.split()) # ['解放しろ、', '全てを。']
改行コード\n
をreplace()
で空文字に置換することで削除できます。
splitlines()
は改行で区切ったリストが返される。
一番下のsplit()
でも似たようなことができますが、こちらはスペースでも区切られるので注意。
文字列削除: スライス機能
txt ="ReleaseAll" print(txt[:3]) #先頭から3文字 print(txt[-3:]) #末尾から3文字 print(txt[3:]) #3文字目から末尾 print(txt[:-3]) #先頭から末尾3文字目 # Rel # All # easeAll # Release
分からなければ下記記事参照。
マルチカーソル便利すぎる
Python関係ないけどAtomでマルチカーソルが使えるsequential-number
が便利すぎる。
Atomの設定
> インストール
> sequential-number
と検索。インストール。
option + ドラッグで行を選択。
control + option + 0でsequential-number
の入力パネルを開く。
sequential-number
で入力する構文は以下
<開始番号> <足し算or引き算> <加算する数値> : <最小桁数>
開始番号だけでもそこからの連番が作れる。
Atomでマルチカーソルの連番入力をするパッケージ「sequential-number」をリリースしました | WebDesign Dackel
ハマったこと
brタグで分割されたデータの取得
解決方法はいくつかあるが、文字列にしてsplit()
が楽そう。
html = ''' <table> <tr> <td> 解放しろ <br/> 全てを。 </td> </tr> </table> ''' soup = BeautifulSoup(html, 'html.parser') print(soup.find('td').text.split()) # ['解放しろ', '全てを。']
ただし、間にスペースやタグがある場合は上記だと正しく取得できない。詳細は下記ページ。
select()で:nth-child()使えない
select()
の引数で:nth-child()
は使えない。
ハマったわけではないけど、:nth-child()
から:nth-of-type()
に鞍替えしました。
Google ColaboratoryでGoogleスプレッドシートを読み書きしてみる - uepon日々の備忘録
リストの複数宣言はできない。
list1 = [1, 2, 3] list2 = [1, 2, 3]
これを1行で記述しようとして▶︎変数同時宣言のように記述すると、
list1 = list2 = [1, 2, 3] print(id(list1)) print(id(list2)) # 140051905329480 # 140051905329480
これだとlist1
とlist2
は同じオブジェクトになるので、list1
の値を更新するとlist2
も更新されてしまう。
list1[0] = 4 print(list1) print(list2) # [4, 2, 3] # [4, 2, 3]
リスト宣言が面倒な場合はリスト内包表記を使う。
print([i for i in [1, 2, 3]]) # [1, 2, 3]
リスト内包表記なら、宣言と処理を1行で表記できる。
複数のin演算子を一つにまとめる: any()
解放しろ、全てを
の文字列中に全
または善
があった場合に真を返したい。
下記では正しく動かない。
if "全" or "善" in "解放しろ、全てを": print("まる") # まる
まる
が返されるので一見正しく見えるけど、"全" or "善"
じゃなくてもまる
になる。
"あ" or "い" in "解放しろ、全てを"
もまる。
そこで、論理和が求められるany()
を使う。
引数はliterableオブジェクト。
if any(i in ("全", "善") for i in "解放しろ、全てを"): print("まる") # まる
参考記事
- スプレッドシート
- PythonでスクレイピングしてGoogleスプレッドシートにデータをまとめる | PeyBlog for SE and PG
- gspreadライブラリの使い方まとめ!Pythonでスプレッドシートを操作する | たぬハック
- Google ColaboratoryでGoogleスプレッドシートを読み書きしてみる - uepon日々の備忘録
- gspreadで指定行にまとめて書き込み - yskma’s blog
- gspread — gspread 3.1.0 documentation
- 関数とか構文全般
- PythonとBeautiful Soupでスクレイピング - Qiita
- Pythonのif文による条件分岐の書き方 | note.nkmk.me
- Pythonの三項演算子(条件演算子)でif文を一行で書く | note.nkmk.me
- いるかのボックス: Beautifulsoup4のtextとstringの違い
- 正規表現一覧
- 【python】複数のin演算子を一つにまとめる方法 - 静かなる名辞
- Pythonの組み込み関数all(), any()の使い方 | note.nkmk.me
- あとで読みたい
- 10分で理解する Beautiful Soup - Qiita
- beautifulsoupのドキュメントの真ん中辺りに書かれている便利な関数 - プログラミングマッチョ
- 参考サイト
- Pythonでスクレイピングまとめ - メモ