Contents
requests&BeautifulSoupでフォームデータを送信する際のざっくりとした手順
Seleniumは動作が遅いのでできるだけrequests&BeautifulSoupするのが望ましい。
- FirefoxのデベロッパーツールのNetworkタブをみて、どういうリクエストとレスポンスのやりとりをしているか観察(Chromeはリダイレクト元のやりとりがみれないのでFirefoxのほうがいい)。
- 必要なフォームデータをBeautifulSoupで集めて、リクエストボディに含めてPOSTする。(クッキー情報はrequests.Session()を使えばよしなにしてくれる)
- 失敗したら1に戻る。
フォームデータを集めるコード。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
sess = requests.Session() res = sess.get(url) soup = BeautifulSoup(res.text, 'html5lib') form = soup.find('form', id='xxx') data = {} for elem in form.find_all(['input', 'textarea', 'select']): if elem.get('name') == None: continue # name属性がなければスルーする if elem.name in ['input', 'textarea']: # チェックされていないラジオボタン・チェックボックスはスルーする if elem.get('type') == 'radio' and elem.get('checked') != 'checked': continue if elem.get('type') == 'checkbox' and elem.get('checked') != 'checked': continue data[elem.get('name')] = elem.get('value') if elem.get('value') else '' elif elem.name == 'select': selected_option = elem.select_one('option[selected="selected"]') if selected_option == None: continue # 選択されたオプションがなければスルーする data[elem.get('name')] = selected_option.get('value') if selected_option.get('value') else '' sess.post(url2, data=data) |
パーサーは基本html.parserを使うけど、世の中HTMLの文法エラーが多い。実験してみるとhtml.parserもある程度は修正してくれるみたいだけど、込み入ったエラーは修正してくれない。html.parserだとエラー部分がざっくりなくなることがある。
html5libはブラウザとおなじように文法エラーを修正しながらパースしてくれるらしい。wordpressの編集画面にも文法エラーがあってかなりはまった…。
requestsでフォームデータ送信する際にUTF-8以外の文字コードでパーセントエンコーディングするには
requestsを使って、文字コードがshift-jisのサーバーと通信する実験を行う。
実験環境
送信したリクエストの中身を確認するには、PHPのビルトインサーバーを使う。ビルトインサーバーを起動して、ルートディレクトリに以下のindex.phpを配置する。
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php echo 'name:' . $_POST['name'] . "\n"; echo 'age:' . $_POST['sex'] . "\n"; // 生のヘッダーを表示したいとき //foreach (getallheaders() as $name => $value) { // echo "$name: $value\n"; //} // 生のメッセージボディを表示したいとき //echo file_get_contents('php://input'); |
あと、PHPのビルトインサーバーの文字コードはUTF-8のようなので、php.iniに以下の行を追加して文字コードをUTF-8以外にしておく。
1 |
default_charset = "Shift_JIS" |
まずはデフォルトのutf-8でフォームデータ送信
1 2 3 4 5 6 7 8 9 10 |
import requests data = {'name': '島田', 'sex': 'male'} res = requests.post('http://localhost:8000', data=data, headers={ 'Content-Type': 'application/x-www-form-urlencoded' }) # headersにContent-Typeを指定しないとサーバーがフォームデータを受け取ってくれない print(res.text) # name:蟲カ逕ー # age:male |
日本語が文字化けしてしまう。requests.postメソッドがutf-8でパーセントエンコードしているのだが、サーバーがShift-jisで解釈しようとしたからだ。
UTF-8以外の文字コードでフォームデータ送信
dataオプションに辞書型でフォームデータを与えると、requests.postメソッドが勝手にUTF-8でパーセントエンコードしてくれる。手間を省いてくれてありがたいんだが、サーバーがShift-JISなどほかの文字コードを使っている場合は文字化けしてしまう。ちなみにrequests.postメソッドには文字コードを指定するオプションはない。
dataオプションに文字列を与えるとパーセントエンコードされないので、以下のようにすればいい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import requests from urllib import parse data = {'name': parse.quote('島田', encoding='shift_jis'), 'sex': 'male'} # 自分でパーセントエンコーディング data = '&'.join(key + '=' + value for key, value in data.items()) # name=%93%87%93c&sex=male res = requests.post('http://localhost:8000', data=data, headers={ 'Content-Type': 'application/x-www-form-urlencoded' }) # headersにContent-Typeを指定しないとサーバーがフォームデータを受け取ってくれない print(res.text) # name:島田 # age:male |
文字化けしていない。日本語部分をUTF-8以外の文字コードでパーセントエンコーディングした文字列をつくって、dataオプションに与えている。
文字列にせずに辞書型のままrequests.postメソッドに与えると、パーセントエンコーディングしたものをさらにパーセントエンコーディングしてしまう。%が%25にエンコードされしまい、name:%93%87%93c となってしまう。
ちなみにクライアント側がshift-jisをデコードできるのはヘッダーに「’Content-type’: ‘text/html; charset=Shift_JIS’」が入ってくるため。
また、4行目でパーセントエンコーディングしないと以下のような例外が発生する。
1 |
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 5-6: Body ('島田') is not valid Latin-1. Use body.encode('utf-8') if you want to send it encoded in UTF-8. |
ヘッダーにContent-Typeを指定しないとサーバーがフォームデータとして受け取ってくれないので注意(フォームデータにかぎらずなにかしらデータをPOSTする際は、メッセージボディの内容に応じてContent-Typeを設定する必要がある)。
1 2 3 4 5 6 7 8 |
data = {'name': parse.quote('島田', encoding='shift_jis'), 'sex': 'male'} data = '&'.join(key + '=' + value for key, value in data.items()) res = requests.post('http://localhost:8000', data=data) print(res.text) # name: # age: |
ヘッダーにContent-Typeを指定しないと、$_POSTになにも入ってこない。
ちなみにdataオプションに辞書型で与えた場合はrequests.postがヘッダーも設定してくれる。なにか事情がないかぎり、dataオプションに与えるデータは文字列ではなく辞書型にするべき。
スクレイピングするメリット
単に便利、というだけではない。いろいろなサイトを攻略しようとするなかで、サイトのつくりかたの勉強になる。たとえば、なるほど画像をダウンロードされないようにcanvas要素に描いてるんだぁとか発見がある。自分のサイトづくりにも活かせる。