即席スクレイピングPythonコード（正規表現とBeautifulSoupの比較付）

#ソースコード
```python
import re
import requests
from bs4 import BeautifulSoup

#ソースを取得
url = 'hogehoge' #ここを検索したいページのURLに
r = requests.get(url)
t = r.text.replace('\n','').replace('\t','')
s = BeautifulSoup(t,'html.parser')

#タイトルを取得

##正規表現
pattern = r'title>(.*)</title'
result = re.findall(pattern,t)
print(result[0])

##BeautifulSoup
print(s.title.text)

#プレイヤー名とスコア一覧取得

##正規表現
pattern = r'font-weight: 700;">(.*?)</span>.*?<span class="scoreTop ppValue">(.*?)</span>' #ここを検索したい箇所のソースに
result = re.findall(pattern,t)
print(result)

##BeautifulSoup
sts = s.select('.songTop.pp') #うまくclassが分離されているならこれも可
scs = s.select('.scoreTop.ppValue')#うまくclassが分離されているならこれも可
result = []
for st,sc in zip(sts,scs):
    result.append((st.text,sc.text))
print(result)
```

#解説

　コピペで実行できる状態を防ぐために上では一応伏せているが、[普段お世話になっているページ](https://scoresaber.com/global)を対象に

- タイトルを取得する
- プレイヤーとスコアの一覧を取得する

　という操作を、正規表現とBeautifulSoup（以下bs）の2種類でやってみる。

```python
#ソースを取得
url = 'https://scoresaber.com/global' #ここを検索したいページのURLに
r = requests.get(url)
t = r.text.replace('\n','').replace('\t','')
s = BeautifulSoup(t,'html.parser')
```

　ここで対象のソースとそのテキストデータを得る。改行やタブが入っていると正規表現の邪魔になるので`replace`でそれらを取り除く。また今後、`str`の括りは`'`（シングルクオーテーション）で行うことを推奨する。なぜならhtmlの中に`"`（ダブルクオーテーション）があってややこしくなるから。

##タイトルを得る

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/3f3b891c-9bd0-a8dd-3070-fbb8dbdef710.png)

　どのhtmlもtitleタグは持っている（と思う。おそらく）ので、それを狙い撃ちする。

```python
##正規表現
pattern = r'title>(.*)</title'
result = re.findall(pattern,t)
print(result[0])
```

　正規表現を使う場合は、`find_all`を使うと便利である。`match`だと前後の文字も含めて一致させないといけないため。ソースから該当箇所をコピーして、タグに挟まれた部分だけ`(.!?)`でグループ分けすると目的の文字列が得られる。ここで`?`をつけるのは最短一致にさせるためである。

```python
##BeautifulSoup
print(s.title.text)
```

　bsでやる場合は、`bs4.BeautifulSoup`クラスは`title`という属性を持っているので直接参照できる。また、`title`は`bs4.element.Tag`というクラスであるが、これは`text`によってタグには中の文字だけを狙い撃ちできる。

```python:output
Global Rankings | ScoreSaber!
Global Rankings | ScoreSaber!
```

##一覧を得る

　この例では、プレイヤーとスコアの一覧を取得しているが、もし活用したい場合は各自ソースコードを眺めて目的の情報を得られるように改造されたい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/cdfd1382-67ba-de12-efab-bf91eaec0f95.png)

　このあたりの情報を抽出したい。

　正規表現を使う場合も、基本的には先程と同様である。目的の情報周辺のソースコードをコピーして、ターゲット部分を`()`でグループ分けする。ここでも逐一`.*?`にして最短一致にしないとカオスになるから注意。

```python
##正規表現
pattern = r'font-weight: 700;">(.*?)</span>.*?<span class="scoreTop ppValue">(.*?)</span>'
result = re.findall(pattern,t)
print(result)
```

　bsで行う場合、タグでうまい感じに分離されていないかを確認する。ソースコードを見ると、`songTop pp`、`scoreTop ppValue`というクラスがそれぞれ1対1対応してそうなので、それでやってみる。クラスは`select`で選択できる。タグの属性で検索する場合は`find_all()`で検索できる。リンク先等を取得する場合は`get`。これは完全に自分用の備忘録である。

```python
##BeautifulSoup
sts = s.select('.songTop.pp')
scs = s.select('.scoreTop.ppValue')
result = []
for st,sc in zip(sts,scs):
    result.append((st.text,sc.text))
print(result)
```

　今回の例ではこれで上手くいく。もし単純なクラス抽出等で上手くいかない場合は、正規表現と格闘する必要があるかもしれない。

```python:output
[('Taichidesu', '13,018.10'), ('Astrella', '12,996.00'), ('Garsh', '12,952.00'), ('sphynx', '12,900.50'), ('cerret', '12,895.00'), ('Duh Hello', '12,872.50'), ('Silverhaze', '12,798.70'), ('bckill', '12,731.40'), ('ptotpa', '12,729.30'), ('Tseska', '12,616.70'), ('CoolingCloset', '12,587.40'), ('Zyrix', '12,573.10'), ('That_Duck56', '12,522.40'), ('SSnowy', '12,450.00'), ('�🔥BALL SACK GAMIN🔥🔥 | Schwak�😳😂😂😖😖', '12,432.90'), ('Squid', '12,416.80
Tyrits', '12,389.10'), ('Smallfox', '12,352.10'), ('epicmoo34', '12,276.90'), ('MOE-ShitMiss Jesus', '12,273.80'), ('Matt_LEBE', '12,255.20'), ('retarde  .', '12,249.10'), ('(づ￣ ³￣)づ *:･Wizz', '12,235.00'), ('FOAA | wolfvr13', '12,229.30'), ('themoldylog', '12,228.00'), ('NDK | Electrostats', '12,179.70'), ('YEP_KingRazer', '12,170.50'), ('Flee', '12,117.20'), ('Reddek', '12,090.90'), ('Drakonno', '12,083.60'), ('samich', '12,054.00'), ('xXIamCzechXx', '12,041.30'), ('Cum Jar Gaming | harbgy', '12,031.60'), ('Silverpoint', '12,005.00'), ('ejiejidayo', '12,004.90'), ('klkk1603', '11,977.20'), ('noob', '11,965.00'), ('Wannrick', '11,962.80'), ('Skyggen', '11,943.30'), ('SHS_MonsterWook', '11,913.50'), ('BSFR - Brindille', '11,883.50'), ('Klow', '11,877.80'), ('CreeperDude425', '11,873.50'), ('Twitch.tv/LSToast', '11,862.90'), ('AtomicX', '11,846.60'), ('Jaack', '11,787.20'), ('Zurph', '11,778.60'), ('TN | FOAA | Monke', '11,777.80'), ('TheJumpingSheep', '11,767.70'), ('AlexTheCutie', '11,746.70')]
[('Taichidesu', '13,018.10'), ('Astrella', '12,996.00'), ('Garsh', '12,952.00'), ('sphynx', '12,900.50'), ('cerret', '12,895.00'), ('Duh Hello', '12,872.50'), ('Silverhaze', '12,798.70'), ('bckill', '12,731.40'), ('ptotpa', '12,729.30'), ('Tseska', '12,616.70'), ('CoolingCloset', '12,587.40'), ('Zyrix', '12,573.10'), ('That_Duck56', '12,522.40'), ('SSnowy', '12,450.00'), ('�🔥BALL SACK GAMIN🔥🔥 | Schwak�😳😂😂😖😖', '12,432.90'), ('Squid', '12,416.80
Tyrits', '12,389.10'), ('Smallfox', '12,352.10'), ('epicmoo34', '12,276.90'), ('MOE-ShitMiss Jesus', '12,273.80'), ('Matt_LEBE', '12,255.20'), ('retarde  .', '12,249.10'), ('(づ￣ ³￣)づ *:･Wizz', '12,235.00'), ('FOAA | wolfvr13', '12,229.30'), ('themoldylog', '12,228.00'), ('NDK | Electrostats', '12,179.70'), ('YEP_KingRazer', '12,170.50'), ('Flee', '12,117.20'), ('Reddek', '12,090.90'), ('Drakonno', '12,083.60'), ('samich', '12,054.00'), ('xXIamCzechXx', '12,041.30'), ('Cum Jar Gaming | harbgy', '12,031.60'), ('Silverpoint', '12,005.00'), ('ejiejidayo', '12,004.90'), ('klkk1603', '11,977.20'), ('noob', '11,965.00'), ('Wannrick', '11,962.80'), ('Skyggen', '11,943.30'), ('SHS_MonsterWook', '11,913.50'), ('BSFR - Brindille', '11,883.50'), ('Klow', '11,877.80'), ('CreeperDude425', '11,873.50'), ('Twitch.tv/LSToast', '11,862.90'), ('AtomicX', '11,846.60'), ('Jaack', '11,787.20'), ('Zurph', '11,778.60'), ('TN | FOAA | Monke', '11,777.80'), ('TheJumpingSheep', '11,767.70'), ('AlexTheCutie', '11,746.70')]
```

#なぜこれを書いたか

　スクレイピングする度にぐぐっている気がしたので、自分の中での定石を定めたかった。あと備忘録。