pythonでwebサイトをスクレイピングするときのtips

# はじめに

スクレイピングすることがあったので、そのときに得た小技の整理です。

# 使った環境

* python 3.x
* [requests](https://requests.readthedocs.io/en/master/)
* [Beautiful Soup 4](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)
* [Pycharm](https://www.jetbrains.com/ja-jp/pycharm/)

## 補足1

windowsでもpythonのインストールはかなりかんたんです。
[pythonの公式サイト](https://www.python.org/downloads/windows/)から、インストーラーをダウンロードして、実行するだけです。

Pycharmをつかえば、venvでの実行環境をつくるのもかんたんです。
pycharmからのterminalで、つくったpythonスクリプトの実行もできます。とくにこだわりがなく、さくっと実行環境をつくりたいならPycharmはおすすめです。（特に、Java系でIntelliJ IDEAをつかっている人に）

## 補足2

ライブラリのインストール

```
pip install requests
pip install beautifulsoup4
```

import文

```
import requests
from bs4 import BeautifulSoup
import math
import re
import csv
```

# リクエストの作り方

## パスパラメタ

これはpythonの文字列に変数を展開するのを使います

```
url1 = "https://sample.com/{path1}/{path2}".format(path1='testA', path2='testB')
```

## クエリパラメタ

[requestsの機能](https://requests.readthedocs.io/en/master/user/quickstart/#passing-parameters-in-urls)を使います。

```
res = requests.get('https://sample.com/test', params={'a': 'testA', 'b': 'testB'})
```

# 0埋めして、連番形式のパラメタを生成

01,02, ... 10 のような、数字の連番で2桁の文字列になっている、というようなパラメタのことがあります。このようなパラメタでfor文回すためのリストを、`range`と`zfill`で生成できます。

```
[str(i).zfill(2) for i in range(1, 11) ]
```

00, ... 09のように連番が0はじまりのときは

```
[str(i).zfill(2) for i in range(10) ]
```

なにがしかのprefixがつく場合は

```
[ 'XX' + str(i).zfill(2) for i in range(10) ]
```

とすると、XX00, ... XX09 となります。

# レスポンスの取扱

beautifulsoupはbyte配列からも読み込めるので、requestsのレスポンスオブジェクトのcontentを使います。このやり方が文字化けが起きにくいようです。

```
res = requests.get(url)
soup = BeautifulSoup(res.content, "html.parser")
```

## クラス名での検索

find_allかfindに、class_パラメタにセットしてやります。
タグを指定したい場合は、最初にそのタグを渡します

```
soup.find_all('a', class_='list-rst__rst-name-target')
```

## 固定のテキストでの検索

textパラメタに正規表現を渡します。


```
soup.find('th', text=re.compile('テスト'))
```

## 改行コード・空白文字の除去

strip()を使えば、だいたいうまくいきます

```
soup.find_all('a', class_='list-rst__rst-name-target').text.strip()
```

## 要素の存在チェック

if式を使えば、そこそこ楽にかけます。

```
tag = soup.find(id='test')
text = tag.text.strip() if tag else ''
```

# ページング

## 最終ページの求め方

検索結果の最初のページなどで、全件の数がわかっているものとします。
1ページあたりの件数もわかっているものとします。

全件/1ページあたりの件数を、少数点切り上げするだけです。
割った結果が少数になるように、全件か、1ページあたりの件数のどちらかをfloatにします。 

```
total_count = float(soup.find('p', id='total_count').string)
max_page = math.ceil(total_count / page_size)
```

## ページングのためのfor文

1始まりなら、こうなります。

```
for page_number in range(1, max_page + 1):
    print(page_number)
```

0はじまりなら、下記のようになります。

```
for page_number in range(max_page):
    print(page_number)
```

# ファイルへの書き込み

項目が決まっている場合は、csv.DictWriterが便利です。

csv、文字コードをUTF-8、囲み文字をダブルクォート、囲み文字をすべてつける場合は下記になります。delimiterを指定すれば、tsvにもできます。

```
with open('test.csv', 'a', newline='', encoding='utf8') as f:
     writer = csv.DictWriter(
            f,
            fieldnames=['id', 'name'],
            quotechar='"',
            quoting=csv.QUOTE_ALL
     )
     id = ...
     name = ... 
     rowdict = {'id': id, 'name': name}
     writer.writerow(rowdict) 
```


# 参考リンク

* https://qiita.com/oppasiri330/items/0f9526a1c507ae170a56
* https://qiita.com/Azunyan1111/items/9b3d16428d2bcc7c9406
* https://qiita.com/akidroid/items/df527a78365437e9b2d0
* https://qiita.com/Atupon0302/items/352811a2d92d6ebab8c2
* https://qiita.com/naka-j/items/ef38498273e036c26f4d
* https://yuki.world/python-requests-beautifulsoup-text-garbling/
* https://qiita.com/shohei-nagano/items/79cc15e22e88b11bb677