Python学習中のためスクレイピングをやってみる。
すでに山ほど参考サイトがあるが、以下がソースも短くライトそうなので拝借し、本家はBeautifulSoupを使っているところをpyqueryに改造してみる。
参考PythonのrequestsとBeautifulSoupでGoogle検索結果から、タイトルとURLと説明文だけを抜き取る
動作環境
動作環境はpyenv+venvで構築しています。構築手順はこちら。
- Mac OS High Sierra
- Python 3.6.8
- pip 18.1
利用モジュール
- requests ··· httpリクエストのためのモジュール
- pyquery ··· htmlパースのためのモジュール
- csv ··· csv書き出しのためのモジュール(csvモジュールはpython標準で利用できるモジュールのため
pip install
不要。)
インストールは以下。
1 2 3 |
(venv) $ pip install pyquery requests Collecting pyquery [以下略] |
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# モジュール読み込み import requests as req from pyquery import PyQuery as pq import csv # 検索キーワード list_keywd = ['機会学習', '統計'] res = req.get('https://www.google.com/search?num=100&q=' + ' '.join(list_keywd)) res.raise_for_status() # HTMLパース query = pq(res.text, parser='html') # 検索結果のタイトルとリンクを取得 link_elem01 = query(".r > a") # 検索結果の説明部分を取得 link_elem02 = query(".s > .st") if(len(link_elem02) <= len(link_elem01)): leng = len(link_elem02) else: leng = len(link_elem01) # CSVファイルを書き込み用にオープンして整形して書き出す with open('output.csv','w',newline='',encoding='utf8') as outcsv: csvwriter = csv.writer(outcsv) csvwriter.writerow(['タイトル', '説明', 'URL']) for i in range(leng): # リンクのみを取得し、余分な部分を削除する url = link_elem01.eq(i).attr('href').replace('/url?q=','') # タイトルのテキスト部分のみ取得 title = link_elem01.eq(i).text() # 説明のテキスト部分のみを取得/余分な改行コードは削除する discription = link_elem02.eq(i).text().replace('\n','').replace('\r','') csvwriter.writerow([url, title, discription]) |
利用メソッド、構文理解
fromでクラスを直接インポート
1 2 3 4 |
# pyqueryモジュールからPyQueryクラスをpqというエイリアスで読み込む from pyquery import PyQuery as pq [中略] query = pq(res.text, parser='html') |
fromで読み込まない場合以下のようになり面倒。
1 2 3 4 5 |
import pyquery [中略] # ここで一旦PyQueryクラスのインスタンスを生成する必要がある pq = pyquery.PyQuery query = pq(res.text, parser='html') |
joinメソッド
1 2 3 4 |
# スペース区切りで文字列を結合する list_keywd = ['機会学習', '統計'] result = ' '.join(list_keywd) print(result) #機械学習 統計と表示される |
Response.raise_for_statusメソッド
Responseオブジェクトのhttpステータスコードが200番台以外の場合はエラーとする。
1 2 |
res = requests.get('https://www.google.com/search?num=100&q=' + ' '.join(list_keywd)) res.raise_for_status() |
PyQuery(“hoge”)
pyqueryで要素を検索する。
要素タグ、id、class、css selecter、要素内に含まれる文字列 など色々検索可能。
1 2 3 4 5 6 |
query = pq(res.text, parser='html') # 検索結果のタイトルとリンクを取得 link_elem01 = query(".r > a") # もしくは以下のようにfindメソッドを明示的に使っても良い link_elem01 = query.find(".r > a") |
with構文
Pythonのwithを使用すると、処理の開始時と終了時に必須の処理を絶対に実行してくれる。
例えば、ファイルのオープンとクローズ、通信の開始と終了、データベースへのセッション開始とクローズ など。
withを使用した場合はファイルのclose処理を省略できる。
1 2 |
with open('output.csv','w',newline='',encoding='utf8') as outcsv: [処理] |
open(‘output.csv’,’w’,newline=”,encoding=’utf8′)
パッと見でわからないのは以下2つ。
- ‘w’ ··· writableファイルのオープンを指定
- newline=” ··· 改行の指定をしない。すなわち改行判断をcsvモジュールに委ねる。
newline=” が指定されない場合、クォートされたフィールド内の改行は適切に解釈されず、書き込み時に \r\n を行末に用いる処理系では余分な \r が追加されてしまいます。csv モジュールは独自 (universal newlines) の改行処理を行うため、newline=” を指定することは常に安全なはずです。
Python 3.6.5 ドキュメント
PyQuery.eq(i)メソッド
PyQueryインスタンス中のn番目の要素を取得。
PyQuery.attr(“hoge”)メソッド
PyQueryインスタンス中のhoge属性を取得。
1 2 |
# リンクのみを取得し、余分な部分を削除する url = link_elem01.eq(i).attr('href') |
ソースには現れないが調べたこと
PyQueryのメソッドで返されるオブジェクトがどんな形をしているのか、どう操作するのか軽く調べた。
以下調査した時のコード。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# モジュール読み込み import requests as req from pyquery import PyQuery as pq import csv # 検索キーワード res = req.get('https://someonesdodo.site/2019/01/24/pyenv_and_venv') res.raise_for_status() # HTMLパース query = pq(res.text, parser='html') # 要素を取得した場合のオブジェクトタイプを確認 print('type(query("h2"))の結果: %s' % type(query("h2"))) # そもそも要素を取得するとどのようなオブジェクトが返るのか確認 print('query("h2")の結果: %s' % query("h2")) # 更にtext()を使ってvalueを取得した場合のオブジェクトを確認 print('type(query("h2").text())の結果: %s' % type(query("h2").text())) print('query("h2").text()の結果: %s' % query("h2").text()) print('query("h2")はイテラブルオブジェクトなのでforで1つずつ表示する処理') i = 1 for h2txt in query("h2"): print('%d番目:' % i) print(type(h2txt)) print(pq(h2txt).text()) i += 1 |
実行結果(少し見やすく整形)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
$ python scraping_jikkenn.py type(query("h2"))の結果: <class 'pyquery.pyquery.PyQuery'> query("h2")の結果: <h2>目次</h2> <h2 id="i0">動作環境</h2> <h2 id="i1">pyenvのインストールと設定</h2> <h2 id="i2">pythonのインストールとバージョン切り替え</h2> <h2 id="i3">venvでpython仮想環境構築</h2> type(query("h2").text())の結果: <class 'str'> query("h2").text()の結果: 目次 動作環境 pyenvのインストールと設定 pythonのインストールとバージョン切り替え venvでpython仮想環境構築 query("h2")はイテラブルオブジェクトなのでforで1つずつ表示する処理 1番目: <class 'lxml.html.HtmlElement'> 目次 2番目: <class 'lxml.html.HtmlElement'> 動作環境 3番目: <class 'lxml.html.HtmlElement'> pyenvのインストールと設定 4番目: <class 'lxml.html.HtmlElement'> pythonのインストールとバージョン切り替え 5番目: <class 'lxml.html.HtmlElement'> venvでpython仮想環境構築 |
PyQueryのインスタンス(query)は当然ながらPyQueryオブジェクトで中身はrequestsで取得したhtmlコードとなっている。
query("h2")
で要素抽出しても、PyQueryオブジェクトなのは変わらずqueryがh2要素だけフィルターされたような感じ。
query("h2").text()
で複数のh2要素の値が空白区切りで一つのstrとなる。
つまり、イテラブルオブジェクトの形を保っているのはquery("h2")
までである。(query("h2").text()
でforを回すと1文字毎のイテラブルオブジェクトと判定されてしまう。)
最後に、query(“h2”)の各要素の値を表示するfor文を回す際には、h2txtはHtmlElementオブジェクトとなっている。そのためHtmlElementを操作するか、pyqueryで操作したいのであれば一度pq(h2txt)
として、pyqueryオブジェクトに変換してやる必要がある。