BeautifulSoupでURLを一括取得

## はじめに

どうも。スクレイピング歴10日の初心者です。
本日もスクレイピングを勉強したので学習内容をQiitaにまとめます。

### 環境
beautifulsoup4
windows11
jupyterlab


### 本記事の趣旨
beautifulsoup4を用いてサイトのURLの一括取得方法を学習したので忘備録兼シェア致します。


### バックナンバー
- [初学者が勉強するSelenium。①](https://qiita.com/The_Boys/items/6d53992ff9323c3c2597)
- [初学者が勉強するSelenium。②](https://qiita.com/The_Boys/items/7b0fa0a75748f65b1edf)
- [seleniumはCopy_full_xpathで少し楽になる](https://qiita.com/The_Boys/items/4c0fa1bcac066de2c643)
- [初学者が勉強するBeautiful Soup①](https://qiita.com/The_Boys/items/da2eb6895ca400730fb1)
- [初学者が勉強するBeautiful Soup②](https://qiita.com/The_Boys/items/43d8837fce179cca237f)
- [Beautiful Soupで画像を取得](https://qiita.com/The_Boys/items/6d7dffc982760e8d8066)
- [BeautifulSoupでURLを一括取得](https://qiita.com/The_Boys/items/14d42da70d26fad6dde2)
- [複数ページにわたる情報の取得。スクレイピング。](https://qiita.com/The_Boys/items/0862cb8a72100519aee9)
- [複数に渡るページでの画像収集スクレイピング](https://qiita.com/The_Boys/items/9486026c20973bc74787)


#### 教材

https://www.youtube.com/watch?v=ANoxy74O_VE&t=884s

上記の動画27分40秒辺りからの内容です。
下記の記事は私がアウトプットの為にまとめたbeautiful soupの記事です。

https://qiita.com/The_Boys/items/da2eb6895ca400730fb1

https://qiita.com/The_Boys/items/43d8837fce179cca237f

https://qiita.com/The_Boys/items/6d7dffc982760e8d8066

## 目標

- [こちらのサイト](https://kino-code.work/python-scraping/)内のURLをまとめて取得したい。
- 他にも条件により取得したいサイトを厳選したい。

最終的にはこのようなdfを取得することを目標とします。

![2022-07-06_13h07_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/ff1c0a56-89f1-e215-a738-9107a46d03a1.png)

## 学習
それでは順々に学習していきましょう。


<details><summary>段階①　モジュールのインポートと要素の確認</summary><div>
必要なモジュールのインポートをします。

```python
# ライブラリやモジュールをimport
from bs4 import BeautifulSoup
import requests
import bs4
import pandas as pd
```

今回学習用に用いるサイトのurlを取得します。
```python
url = "https://kino-code.work/python-scraping/"
res = requests.get(url)
res
```
<Response [200]>と返ってきたらここまで正常に動作しております。

取得したurlのhtml情報を確認します。このhtmlの中から必要な情報を抽出しましょう。

```python
parse_html = BeautifulSoup(res.text, "html.parser")
print(parse_html.prettify())

#結果
<!DOCTYPE html>
<html lang="ja">
 <head>
  <script async="" src="https://www.googletagmanager.com/gtag/js?id=UA-140862964-1">
  </script>
  <script>
・
・
・
```

htmlの情報を取得することが出来ました。しかしこのままでは多すぎるので<font color="red">aタグ</font>のみ取得します。htmlの構造上､urlはaタグに含まれているのでその部分を一括取得します。


![2022-07-06_14h10_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/403800fe-0bd7-18dd-6fec-27d95ee80a0b.png)


```python
print(parse_html.find_all("a"))
```
いかがでしょうか。実行結果に大量のaタグが表示されたと思います。

:::note info
<font color="red">find_all</font>はbeautiful soupで取得したurlの中から指定した要素を一括取得するメソッドです。
取得した要素は<font color="red">リスト</font>にて返されます。
:::


![2022-07-06_13h59_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/df52941f-03a5-8a95-ee30-f333ea36d246.png)

ひとまずこれらaタグの束を変数に格納しましょう。
```python
title_lists = parse_html.find_all("a")
```

</div></details>


<details><summary>段階②　要素の取得</summary><div>

find_allで抽出したのでtitle_listsはリスト型です。それぞれの内容を確認してみましょう。
.string/.textでテキストのみを抽出する事が出来ます。


```.tsx
title_lists[3].string
title_lists[4].string
title_lists[5].string

#結果
Python超入門コース#02 Pythonとは？
Python超入門コース#03 環境構築 for Mac
Python超入門コース#04 プログラムの基本構造
```

リストなので今度はfor文を用いて全て抽出してみます。


```python
for a in title_lists:
    print(a.string)

#結果

None
ホーム
Python超入門コース#01 Pythonのコース紹介
Python超入門コース#02 Pythonとは？
Python超入門コース#03 環境構築 for Mac
Python超入門コース#04 プログラムの基本構造
Python超入門コース#05 実行
Python超入門コース#06 変数
Python超入門コース#07 データ型
Python超入門コース#08 リスト
Python超入門コース#09 演算子
Python超入門コース#10 条件分岐
Python超入門コース#11 反復
Python超入門コース#12 関数
Python超入門コース#13 クラス
Python超入門コース#14 実践
キノコード
PHP超入門コース#02 PHPとは？
PHP超入門コース＃01コース紹介
Go超入門コース#02 Goとは？
Go超入門コース#01 Goのコース紹介
【Go言語 超入門コース】15.実践｜すべて理解できればGoの超入門者から卒業です【プログラミング初心者向け入門講座】
None
None
None
None
None
None
None

```


いくつかNoneがありましたが、aタグの文字列の部分のみ抽出する事が出来ました。
同じ要領でurlの部分も抽出してみましょう。



```python
for a in title_lists:
    print(a.attrs["href"])

#結果

https://kino-code.work/
https://kino-code.work
https://kino-code.work/course-python01-course-introduction/
https://kino-code.work/course-python02-what-python/
https://kino-code.work/course-python03-environment/
https://kino-code.work/course-python04-basic-structure/
https://kino-code.work/course-python05-runtime/
https://kino-code.work/course-python06-variable/
https://kino-code.work/course-python07-data-type/
https://kino-code.work/coruse-python08-list/
https://kino-code.work/course-python09-operator/
https://kino-code.work/course-python10-conditional-branch/
https://kino-code.work/course-python11-repetition/
https://kino-code.work/course-python12-function/
https://kino-code.work/course-python13-class/
https://kino-code.work/course-python14-practice/
https://kino-code.work/author/kino_tsurayasu/
https://kino-code.work/course-php02-what-php/
https://kino-code.work/course-php01-course-introduction-2/
https://kino-code.work/course-go02-what-go-2/
https://kino-code.work/course-go01-what-go/
https://kino-code.work/course-go15-practice/
https://kino-code.work/2021/10/
https://kino-code.work/2020/05/
https://kino-code.work/2020/04/
https://kino-code.work/2020/03/
https://kino-code.work/2020/02/
https://kino-code.work/2020/01/
https://kino-code.work/

```

urlのみを抽出する事が出来ました。

どちらもfor文の基本で､
1. title_listsの中にある複数の要素(aタグ)を一つ変数aに格納する
1. それら一つづつをを文字列のみ/urlのみ状態にする
1. printで表示する
1. 一つ終わったら次の一つに回る

とのプロセスで回っております。

</div></details>




<details><summary>段階③　for文で効率良く要素の取得</summary><div>
前セクションでaタグのテキストとurlをそれぞれをfor文で取得できました。
今回は同時に取得してそれぞれ対応させてみましょう。

まずは初めにインスタンス(入れ物)を作成します。この中に名前やurlが格納されます。
その後にfor文を構築していきましょう。このような記述はいかがでしょうか？

```python
name_list = []                           #nameインスタンスの作成
url_list  = []                           #url インスタンスの作成

for i in title_lists:                   #for文で1つづつ要素を回す。
    name_list.append(i.string)         #title_listに回ってきた要素のテキストのみ状態を格納
    url_list.append(i.attrs["href"])    #name_list に回ってきた要素のurlのみ状態を格納


```
コメントで解説も加えておきました。for文は私のようなプログラミング初心者には中々とっつきにくいですので。とにかく、これでリストの中にはそれぞれの要素が格納されているはずです。

最後にこのリスト２つを辞書型にして出力しましょう。


```python
#辞書のkeyにname_listを格納し名前は"Title"、valueにurl_listを格納し名前は"URL"としています。

df_name_url = pd.DataFrame({"Title":title_list, "URL":url_list})
df_name_url

```


</div></details>


ここまでのまとめ。
必要な部分のみ記載しております。

```python
from bs4 import BeautifulSoup
import requests
import bs4
import pandas as pd

url = "https://kino-code.work/python-scraping/"
res = requests.get(url)

parse_html = BeautifulSoup(res.text, "html.parser")
title_lists = parse_html.find_all("a")

name_list = []
url_list = []

for i in title_lists:
    name_list.append(i.string)
    url_list.append(i.attrs["href"])

df_title_url = pd.DataFrame({"Title":name_list, "URL":url_list})
df_title_url

```


<details><summary>段階④　取得要素の確認</summary><div>
ひとまず、思った通りに行きましたが、この状態ではnoneが目立ちますね。
noneを全て抽出しましょう。下記のやり方はいかがでしょうか？

```python
#欠損値の削除
df_title_url = df_title_url.dropna(how='any')
df_title_url
```

これでnoneは削除されました。`dropna`は欠損値Nanを削除するメソッドです。
そして()の中の引数`any`ですが、<font color="red">欠損値が一つでも含まれる行が削除</font>されるというオプションです。因みにhow='any'はデフォルトなので()の引数は何もなしでも大丈夫です。

- 結果

![2022-07-06_15h01_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/da206bb8-6e12-b836-7956-88700b6689f7.png)

</div></details>


<details><summary>段階⑤　取得要素の絞り込み</summary><div>
これで欠損値もなくなり良い感じですが、少し項目が多いですね。少し削りましょう。
今回<font color="red">python</font>に関係ある事のみをdfに残そうかと思います。


```python
#pythonのみ抽出
df_title_url = df_title_url[df_title_url['Title'].str.contains('Python')]
df_title_url
```
このコードではいかがでしょうか？



結果を表示します。

![2022-07-06_15h06_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/fe132d9f-8652-ebab-7b37-8073e90bf2aa.png)
かなりまとまった結果になりました。今回はこれでゴールとします。


追伸：上記のコード`df_title_url = df_title_url[df_title_url['Title'].str.contains('Python')]
`は私自身、理解が曖昧であった為、補足説明致します。

まずは、`df_title_url['Title'].str.contains('Python')`とはどのような意味でしょうか？
これは<font color="red">dfであるdf_title_urlのカラム"Title"の中に文字列"python"が含まれているもの</font>　という意味です。実際に実行してみましょう。


```python
df_title_url_python = df_title_url['Title'].str.contains('Python')
df_title_url_python 
```
結果

![2022-07-06_20h44_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/47961003-4d19-37d3-d9f8-1c3db89dd011.png)

画像の通り､bool型を返し､判断は可能です。しかし目的は異なりますよね。
目的は<font color="red">そのtrueの要素を抽出してdfにしたい。</font>という事です。

その為にはこのように


```python
df_title_url_python = [df_title_url['Title'].str.contains('Python')] 
df_title_url_python
```
`[]で括る`必要があります。そうすれば、**bool型でtrueのみ抽出してdfに起こす事が出来ます。**
</div></details>

総まとめのコード


```python
from bs4 import BeautifulSoup
import requests
import bs4
import pandas as pd

url = "https://kino-code.work/python-scraping/"
res = requests.get(url)

parse_html = BeautifulSoup(res.text, "html.parser")
title_lists = parse_html.find_all("a")

name_list = []
url_list = []

for i in title_lists:
    name_list.append(i.string)
    url_list.append(i.attrs["href"])

df_title_url = pd.DataFrame({"Title":name_list, "URL":url_list})
df_title_url

#欠損値の削除
df_title_url = df_title_url.dropna(how='any')
df_title_url

#pythonのみ抽出
df_title_url = df_title_url[df_title_url['Title'].str.contains('Python')]
df_title_url
```

## 終わりに

今回はスクレイピングを楽しく学び、業務に役立てる気配を感じました。
しかし具体的にはどのようにスクレイピング技術を役立てられるかはまだ思いつかず模索中です。
そしてfor文やpandasの1部もスクレイピングを学習し、Qiitaにまとめるまでは理解しにくい所もあったのですが、その部分も浮き彫りにして解消できて何よりでした。

近々の目標はスクレイピングの技術を用いて簡単なアプリや自動化のシステムを作成する事です。