LoginSignup
1
5

More than 1 year has passed since last update.

Python Webスクレイピング テクニック・効率化

Last updated at Posted at 2022-09-17

はじめに

スクレイピングを使い始めて約1ヶ月が経ちました。
そこでスクレイピングで使う、ちょっと便利な小技(当たり前のことだったらすみません)をまとめましたので、どなたかの役に立てば幸いです。
(念願のQiita初投稿です!! アドバイスや間違いがあれば、ビシバシご指導よろしくお願い致します!)

※ その前に・・・ 完全初心者の方へ

以下の動画が大変参考になります。これを見ると一通りはスクレイピングできるようになると思います。

動画でのSeleniumとバージョンが違う場合、find_elementの使い方が若干異なります。

elem = browser.find_element_by_id("user_name")
→ find_element("id", "user_name")

小技集

本題に入ります。

小技その1 〜もっとみるボタンの押下〜

サイトによっては「もっと見る」ボタンがあり、それをクリックしないとページ内のすべてのデータが取得できません。
Seleniumの .click() を用いて全データを取得します。

example1.py
# ◯件中◯件のような全件数が表示されている部分を取得
elem_alls = browser.find_element('xpath', '/html/body/div[6]/div[2]/div[4]/div/div[2]/div[2]/span[1]').text

# 全件表示するまでボタンを押す
while True:

    # 現在の表示数
    elem_counts = browser.find_element('xpath', '/html/body/div[6]/div[2]/div[4]/div/div[2]/div[2]/span[2]').text
    
    # 最大まで表示されたらボタンを押すのを止める
    if elem_counts == elem_alls:
        break
    else:
        elem_btn = browser.find_element('class name', 'moreBtn')
        elem_btn.click()
        time.sleep(5)

以降は要素を指定してデータを取得するだけです。全件表示後のデータから抽出できるはずです。

小技その2 〜requestsで取得できないデータを抽出〜

soup = BeautifulSoup(res.text, "html.parser")とスクレイピングしていると、実際に閲覧している内容と取得したデータが異なることがあります。「ダウンロードしたHTMLファイル」と「ブラウザに表示されるHTML」が異なるかららしいです。 (参考ページ)
以下のようにSeleniumを用いた関数を作ることで回避します。

example2.py
# 実際に閲覧している内容を返す
def rendering(url):

    options = webdriver.ChromeOptions()
    options.add_argument('--headless')
    driver = webdriver.Chrome(ChromeDriverManager().install(), options=options)

    driver.get(url)
    time.sleep(5)
    
    html = driver.page_source
    soup = BeautifulSoup(html, "html.parser")
    driver.quit()
    
    return soup

この関数にURLを渡すと、あとはいつものように解析できます。

小技その3 〜スクレイピングの並列化〜

スクレイピングにめちゃめちゃ時間がかかることがあります。並列処理を用いて高速化させることができます。

example3.py
from multiprocessing import Pool

# データフレーム、関数、分割数を受け取って並列処理させる関数
def splitter(df, func, cores): 
    df_split = np.array_split(df, cores)
    pool = Pool(cores)
    df = pd.concat(pool.map(func, df_split))
    pool.close()
    pool.join()
    return df

# 何らかの処理をさせる関数
def get_info(df): 
    # プログレスバー表示
    tqdm.pandas()
    
    _df = df.progress_apply(lambda x : get_urls(x['colum_name']) if x['colum_name']!='https://example.html' else get_unique(x['0']), axis = 1)
    return _df

# 実際の処理
if __name__ == '__main__':
    df = pd.read_csv('example.csv')
    _df = splitter(df, get_info, 4)

小技その4 〜保存しながらスクレイピング〜

取得件数が大きくて、あと少しで終わるときにエラーが起きてしまうと泣きそうになります。こまめに何らかのデータとして書き出しておくことで、最悪の事態を免れます。(エラーが起きないようにコードを書けたら一番いいのですが・・・)

example4.py
# urlが入っているcsvデータ
df = pd.read_csv('url_list.csv', index_col=None)

# 初めて実行する場合はこちら
_df = pd.DataFrame()

# エラーが起きて再実行する場合はこちら
_df = pd.DataFrame(product_info.csv)

# 100件データを取得したらcsvに書き出す
for i in range(0, len(df), 100): # エラーが起きた場合はstartの位置を変えます。
    df_opt = pd.DataFrame()
    tqdm.pandas()
    
    # 並列処理の関数
    df_opt = splitter(df[i:i+100], get_info, 4)
    _df = pd.concat([_df, df_opt])

    _df.to_csv('result.csv', encoding = 'utf_8_sig', index = False)
    
    # エラーが起きたときのため、どこまでやったか出力させる
    print(i)

小技その5 〜文字列の整形〜

取得したデータには改行コード、タブ、スペースなどが含まれることが多々あります。いちいちreplaceを使っていたら面倒ですが、関数を定義することで一気に除去できます。(参考ページ)

example5.py
# 文字列のノイズを除去する関数
def processString(txt):
    specialChars = "\u3000 \n\xa0"
    for specialChar in specialChars:
        txt = txt.replace(specialChar, "")
    return txt

reshape = processString(i.find_all('div')[1].text)

おわりに

ここまで読んでいただきありがとうございました。かなり疲れました(笑)
これを機にある程度知識を蓄えたらばんばん記事にしていこうと思います!

1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5