スクレイピングの学習 Python3系 その6

# 前回の内容
[スクレイピングの学習 Python3系 その5](https://qiita.com/mizssy07/items/549ff63b82f7b713445e)

# 改善点
- 元リンクの取得
- 最終ページの記事が取得出来ていなかったのでループ処理を改修
- 一覧外のランキング記事などを取得しない
- タブで分かれたページをすべて取得

# 手順
- 一覧部分のdivを一旦取得してから要素を探索
- 直リンクのURLを取得
- 次ページ判定のif文を記事取得後に移動
- 記事取得ループを関数化してタブごとに関数呼び出し

# 変更・追加したコード
```python:
# divタグの中から、class=list_itemの含まれたものを取得(リスト形式になる)
div = soup.find("div", {"class": "content_list"})
tags = div.find_all("div", {"class": "list_item"})

# 抽出した要素を行列に入れ込む
for tag in tags:
    title = tag.find("a", {"class": "title"}).text  # 記事名の取得
    url = tag.find("div", {"class": "comment"}).find("a").get("href")  # リンクの取得
    se = pd.Series([title, url], columns)  # 行を追加
```

# 全体のコード
```python:
# coding: UTF-8
from selenium import webdriver
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import pandas as pd
import requests
import datetime
import time

# プログラム上からブラウザを操作する
browser = webdriver.Chrome(executable_path="chromedriverのある場所のパス")

# ページ推移用のURL指定(相対URLを絶対URLに変換する用)
originalUrl = "https://newstopics.jp/"
# データフレームを作成。列名 title=記事名, url=url
columns = ["title", "url"]
df = pd.DataFrame(columns=columns)


# 各タブごとに同一の処理を行うため記事の取得を関数化する
def get_article(category):

    # 引数に合わせ、トップページのURLを変更
    url = urljoin(originalUrl, category)
    browser.get(url)  # urlを開く
    page = 1  # ページ数を保存する変数を設定
    global df  # スコープ外で指定した変数の呼び出し

    while True:  # 最終ページまで処理を続ける

        # htmlを取得、BeautifulSoupで扱う
        html = requests.get(url)
        # BeautifulSoupの初期化
        soup = BeautifulSoup(html.content, 'html.parser')
        # 「次へ」を数える
        nextpage = len(soup.find_all("a", {"rel": "next"}))

        # 取得中のページ数を表示
        print("###################page: {} #####################".format(page))
        print("Starting to get posts...")

        # divタグの中から、class=list_itemの含まれたものを取得(リスト形式になる)
        div = soup.find("div", {"class": "content_list"})
        tags = div.find_all("div", {"class": "list_item"})
        # 抽出した要素を行列に入れ込む
        for tag in tags:
            title = tag.find("a", {"class": "title"}).text  # 記事名の取得
            url = tag.find("div", {"class": "comment"}).find("a").get("href")  # リンクの取得
            se = pd.Series([title, url], columns)  # 行を追加
            print(se)  # 行列を表示 <= デバッグ用
            df = df.append(se, columns)  # 新規の行を作成

        if nextpage > 0:  # 次へがある場合の処理
            # 次のページへ移動する
            nextpage_url = soup.find("a", {"rel": "next"}).get("href")  # 次へを探す
            # 相対urlを元urlと組み合わせて絶対urlへ => urlを上書き
            url = urljoin(originalUrl, nextpage_url)
            print("next url:{}".format(url))
            browser.get(url)  # 次ページへジャンプ
            page += 1  # ページ数のインクリメント
            browser.implicitly_wait(10)  # 10秒待機(表示待ちの待機)
            print("Moving to next page......")
            time.sleep(10)  # 10秒待機(サイトへの負荷軽減)

        else:  # 次へがない場合に処理を終了
            print("no pager exist anymore")
            break


get_article("categories/soft")  # ゆるいニュース => categories/soft
get_article("categories/hard")  # かたいニュース => categories/hard

# 重複した内容を削除する
print("Deleting duplicate rows...")
df.drop_duplicates(subset="title", inplace=True)

# CSVに出力
today = datetime.datetime.now().strftime("%y%m%d%H%M%S")  # 今日の日付をファイル名に
filename = today + ".csv"
df.to_csv(filename, index=False, encoding='utf-8')  # encoding指定、おまじない
print("DONE")

# ブラウザを終了する
browser.close()
```

# 補足
- 元リンクへのURLはもともと取得していた部分になかったので、一つ上の要素から取得
- `find`を連続で書くのは大丈夫。`find_all`だとリスト形式になっちゃうので駄目
- 次ページの有無による判定を前に置いていると、記事の取得前にループを抜けてしまい最終ページの取得ができない
- タブで別れた部分は`urljoin`を利用
- 記事の取得を関数化して、引数に相対URLを指定

# 次回への課題
- ~~CSVでダウンロード~~
- ~~重複する内容を取得しないようにする~~
- ~~次ページ以降の取得~~
- ~~元リンクを取得~~
- ~~ローカル環境で動かす~~
- ~~タブで別れたジャンルをすべて取得~~