【ラクラク画像収集→保存】04.検索結果ページの画像を自動保存|PythonでWebスクレイピング

こんにちは。キノコードです。キノコードでは、今までWebスクレイピングの動画を3本出してきました。
1つは、ブラウザを操作して、サイトにログイン。サイトにアップされているデータをダウンロードしてくるレッスン。
2つめは、複数ページに記載があるリンク先URLを取得する方法
3つめは、検索結果にでてきたページの情報を取得する方法
そしてこの今回の動画は、画像を保存する方法について解説をします。
自社サイトに掲載されている画像を保存する場合などに、圧倒的に作業効率化できます。なお、ウェブスクレピングには利用規約違反や法律違反になる可能性もあります。
ウェブスクレピングの1回目の動画にウェブスクレイピングの注意点についてはお話ししているので一度ご確認ください。
キノコードのウェブサイトにも注意点を記載しておきますので、そちらもご覧ください。キノコードでは、この動画の他にも、たくさんのプログラミングのレッスンを配信しています。
チャンネル登録がまだの方は、チャンネルがどこにいったかわからなくならないように、チャンネル登録をお願いします。また、キノコードではメンバーシップをやっています。
キノコードの動画制作活動を応援してくださる方は、月額290円から応援できます。
詳しくはチャンネル登録ボタンの隣にある「メンバーになる」をクリックしてください。
キノコードのメンバーシップの解説動画があります。それではレッスンスタートです。

【ラクラク画像収集→保存】04.検索結果ページの画像を自動保存|PythonでWebスクレイピング
from bs4 import BeautifulSoup
import requests
import pandas as pd
import time

では、ライブラリをインポートしていきます。
bs4のパッケージの中にあるBeautifulSoupを読み込みます。
BeautifulSoupは、複雑なHTMLの構造を解析し、必要な部分を取り出すことができるライブラリです。
HTTP接続するために、requestsをインポートします。
requestsはPythonの標準のurllibモジュールよりもシンプルに実装でき、APIに関する機能も備わっています。
データ整形のためにPandasもインポートします。
さらに、HTTPに接続した後、すぐに次の処理が実行されないようにするためtimeをインポートします。
実行します。
インポートが完了しました。

keyword = 'Python'

url = 'https://kino-code.work/?s={}'.format(keyword)

r = requests.get(url)
time.sleep(3)

soup = BeautifulSoup(r.text, 'html.parser')
page_na = soup.find(class_ = 'pagination')
page_num = page_na.find_all(class_ = 'page-numbers')

pages = []
for i in page_num:
    pages.append(i.text)

urls = []

if not pages:
    urls = ['https://kino-code.com/?s={}'.format(keyword)]
else:
    last_page = int(pages[-2])

    for i in range(1, last_page + 1):
        url = 'https://kino-code.work/?s={}'.format(keyword) + '&paged={}'.format(i)
        urls.append(url)

urls
['https://kino-code.work/?s=Python&paged=1',
 'https://kino-code.work/?s=Python&paged=2',
 'https://kino-code.work/?s=Python&paged=3']

前回のレッスンで書いたソースコードです。
Pythonで検索しました。
検索結果のページのURLが取得できていますね。

links = []
titles = []
snippets = []

for i in range(len(urls)):

    r = requests.get(urls[i])
    time.sleep(3)
    soup = BeautifulSoup(r.text, 'html.parser')
    # find_allの引数に、HTMLタグが持つCSSのクラスで検索をかける。
    get_list_info = soup.find_all('a', class_ =  'entry-card-wrap')

    for n in range(len(get_list_info)):
        # リンクを取得
        get_list_link = get_list_info[n].attrs['href']
        links.append(get_list_link)

        # タイトルを取得
        get_list_title = get_list_info[n].attrs['title']
        titles.append(get_list_title)

        # 説明文を取得
        get_list_snippet = get_list_info[n].find(class_ = 'entry-card-snippet').text
        snippets.append(get_list_snippet)

リストurlsのURLを1ずつ読み込み、各ページ内の情報を取得していきましょう。
リンク、タイトル、説明文を格納するリストをそれぞれ、links、titles、snippetsとします。
for文で、len丸括弧の中にurlsを記述し、urlsの個数をカウントし、range関数で開始から終了までの数字を取得します。
for文内で、リストurlsにあるURLのWebページを読み込んで、変数rに格納します。
Webページが読み込まれる前に、次の処理に行かないように、time.sleep(3)で3秒待機させます。
そして、BeautifulSoup(r.text,'html.parser')で、読み込まれたWebページのHTMLを解析します。

次に、検索結果のページに表示されている、クラス「entry-card-wrap」内のHTMLを解析していきます。
「entry-card-wrap」のクラス内にある、aタグを全て取得します。
find_all丸括弧の第一引数に"a"、第二引数にclass_イコール"entry-card-wrap"と記述します。
クラス「entry-card-wrap」内に、複数のaタグがあるので、forループでさらに、1つずつ取り出していきます。
aタグの個数分をカウントして、aタグを1つずつ取り出します。
タグ内にある、href属性とtitle属性を取得します。
リストlinksとtitlesにappendで取得した値をそれぞれ入れていきます。

タイトルの下にある、動画の説明文を取得します。
説明文は,クラス「entry-card-wrap」内にあるdivタグのクラス「entry-card-snippet」にあるテキストを取得していきます。
find丸括弧の中に、class_イコール"entry-card-snippet"、ドットtextを追加して、テキストのみを取得します。
リストsnippetsにappendで取得した値を入れていきます。

links
['https://kino-code.work/python-scraping/',
 'https://kino-code.work/course-python14-practice/',
 'https://kino-code.work/course-python13-class/',
 'https://kino-code.work/course-python12-function/',
 'https://kino-code.work/course-python11-repetition/',
 'https://kino-code.work/course-python10-conditional-branch/',
 'https://kino-code.work/course-python09-operator/',
 'https://kino-code.work/coruse-python08-list/',
 'https://kino-code.work/python-super-basic-course/',
 'https://kino-code.work/course-python07-data-type/',
 'https://kino-code.work/course-python06-variable/',
 'https://kino-code.work/course-python05-runtime/',
 'https://kino-code.work/course-python04-basic-structure/',
 'https://kino-code.work/course-python03-environment/',
 'https://kino-code.work/course-python02-what-python/',
 'https://kino-code.work/course-python01-course-introduction/',
 'https://kino-code.work/course-go14-method/',
 'https://kino-code.work/course-go03-environment-for-windows/',
 'https://kino-code.work/course-go07-data-type/',
 'https://kino-code.work/',
 'https://kino-code.work/course-go03-environment/',
 'https://kino-code.work/course-go02-what-go/']

実行します。
各ページ遷移先のURLのリストを取得することができました。
今回は取得したこのURLを使って、各ページに掲載されている画像を取得していきます。

links[0]
'https://kino-code.work/python-scraping/'

リストであるlinksの要素10番目だけで動きを確認してみましょう。
このURLです。

r = requests.get(links[10])
r

requestsのgetメソッドで、指定したurlを呼び出す記述をします。
引数には、linksの要素10番目を渡します。
実行します。
<Response [200]>が返ってきました。
これは、リクエストが成功したことを示しています。

soup = BeautifulSoup(r.text, 'html.parser')

BeautifulSoup(r.text,'html.parser')で、linksの要素10番目のWebページのHTMLを解析します。
解析した結果を変数soupに代入します。

get_image_info = soup.find_all('img')

次に、find_allメソッドを使って画像データを取得します。
取得したデータを変数get_image_infoに代入しましょう。

get_image_info

[<img alt="KinoCodeWork" class="site-logo-image header-site-logo-image" height="100" src="http://kino-code.work/wp-content/uploads/2019/07/logo-1.png" width="535"/>,
<img alt="" class="attachment-1280x720 size-1280x720 eye-catch-image wp-post-image" height="720" loading="lazy" sizes="(max-width: 1280px) 100vw, 1280px" src="https://kino-code.work/wp-content/uploads/2020/01/Python06.jpg" srcset="https://kino-code.work/wp-content/uploads/2020/01/Python06.jpg.pagespeed.ce.XrlpOcRdwX.jpg 1280w, https://kino-code.work/wp-content/uploads/2020/01/Python06-300x169.jpg 300w
,#以下省略

get_image_infoの中身を確認しましょう。
実行します。
データが格納されているようです。

get_image_info[0].attrs['src']
'http://kino-code.work/wp-content/uploads/2019/07/logo-1.png'

画像が保存されているリンク先は、src属性にあります。
srcはsourceの略です。
src属性を取得しましょう。
実行します。
取得することができました。

images_list = []

for i in range(len(links)):

    r = requests.get(links[i])
    time.sleep(3)
    soup = BeautifulSoup(r.text, 'html.parser')
    get_list_image = soup.find_all('img')

    for n in range(len(get_list_image)):
        # 画像を取得
        get_image_link = get_list_image[n].attrs['src']
        images_list.append(get_image_link)

次に、リストlinksのURLを1つずつ読み込み、各ページ内の情報を取得してみましょう。
画像のリンクを格納するリストを定義します。
変数名をimages_listとします。

for文で、len丸括弧の中にlinksを記述し、linksの個数をカウントし、range関数で開始から終了までの数字を取得します。
ループ内で、変数rに、リストlinksの要素であるWebページを読み込んでいきます。
Webページが読み込まれる前に、次の処理に行かないように、time.sleep(3)で3秒待機させます。
BeautifulSoup(r.text,'html.parser')で、読み込まれたWebページのHTMLを解析します。

次に、find_allメソッドを使って画像データ取得します。
この取得した画像データをget_list_imageに格納します。

複数のimgタグがあるので、forループでさらに、1つずつ取り出していきます。
imgタグの個数分をカウントさせて、imgタグを1つずつ取り出します。
タグ内にある、src属性を取得します。
リストimages_listにappendで取得した値をそれぞれ入れていきます。

# images_list

実行します。
画像の保存先のリンクを取得することができました。

len(images_list)
273

画像の保存先のリンクをカウントしてみましょう。
len丸括弧の中にimages_listをかきます。
実行します。
273個の要素があることが分かります。
このリンク先から画像を取得していきましょう。

import os

osライブラリをインポートします。
osは、基本ソフトウェアの機能を使用するためのライブラリです。
ファイル名を取得したり、ディレクトリを作成したり、ファイル名やディレクトリを削除するために使用します。

os.mkdir('./images') # imagesフォルダを作成します。

画像ファイルを保存するディレクトリを作成します。
ディレクトリの作成には、osライブラリのmkdirメソッドを使用します。
os.mkdir丸括弧。
丸括弧の中には、ディレクトリパスを記述し、シングルクォーテーションでくくります。
今回は、imagesというディレクトリを作成します。
なお、このドットは、現在のディレクトリを意味しています。
現在のディレクトリとはJupyterLabを使用している場合、JupyterLabが置いてある場所です。
この現在のディレクトリからスラッシュをつけると、下の階層という意味になります。
つまり、現在のディレクトリの下の階層に、imagesというディレクトリを作成する、というコードです。
実行します。
新しいディレクトリが作成されました。

images_list[0] # リストの一番最初を抽出します。

images_listの最初の要素を見てみます。
imgees_listに角括弧に数字の0を書きます。
実行します。
取得することができました。
画像を保存するときにファイル名をそのまま使用したいと思います。
リンク先をみると、画像のファイル名は/で区切ったときの一番最後の部分です。

images_list[0].split('/') # /で区切ってリストに入れます。

そこで、splitを使ってみましょう。
splitを使うと、splitの引数で指定したもので分解して、リストにすることができます。
実行します。
このように分解したリストを取得できました。

images_list[0].split('/')[-1] # 画像ファイルを抽出します。

一番最後の要素を取得します。
角括弧の中に数字の-1を記述します。
実行します。
画像ファイル名を取得することができました。

# 画像ファイルを保存します。
for image_data in images_list:
    r = requests.get(image_data)
    image_file = open('./images/' + image_data.split('/')[-1], 'wb')
    image_file.write(r.content)
    image_file.close()
# 画像の重複があるので、273個はありません。

このファイル名を使って、画像を保存していきましょう。
リストのimages_listをfor文で回して、画像が保存されているURLをimage_dateに渡します。
そして、画像が保存されているページの情報を、getメソッドで取得します。
Webページが読み込まれる前に次の処理にいかないように、time.sleepで3秒待機させます。
画像の保存には、open関数を使います。
open関数を使うと、ファイルを読み込んだり書き込んだりすることができます。今回は書き込み用で使います。
open関数の第一引数にファイル名を書きます。
そしてmodeの引数に、ファイルを読み込みようで開くのか書き込み用で開くのか渡します。wだと書き込み用、rだと読み込み用です。
今回は、書き込み用の中でもバイナリデータで書き込むので、wbを渡します。
そして生成したオブジェクトをimg_fileという変数に代入します。
次に、ファイルを書き込む記述です。
実際の書き込みは、write関数で書き込むことができます。
変数のimg_fileはバイナリデータを書き込む設定にしているので、丸括弧の中はバイナリデータを渡します。
バイナリデータとは、画像のファイルのデータを、数字と記号に変換したものです。
そして最後に、closeでファイルを閉じるような記述をしましょう。

実行します。
それではフォルダに行き、画像を確認してみましょう。
画像が保存されているようです。

 files = os.listdir('./images')
 files

それでは保存した画像の数をカウントしてみましょう。
ディレクトリにあるファイル名を取得するには、OSライブラリのlistdir関数で確認します。
listdir関数の引数には、ァイルを読み取りたいディレクトリパスを記述します。
それをfilesに入れます。
実行します。
ファイル名を取得できました。

print('画像を' + str(len(files)) + '個保存しました。')

それではファイル名をカウントしてみましょう。実行します。
76個でした。
画像の保存先であるリンク先は、273個ありました。
一方で保存された画像は76個です。この違いは理由は簡単で、画像ファイルに重複があるためです。