メルカリの商品検索結果をスクレイピングして、価格情報、サムネイル画像と詳細ページへのリンクをExcelに出力する方法を解説します。
メルカリで商品を探すときに、検索結果が数ページに渡るとページ送りをするのも面倒だと思います。こちらで解説する方法でExcelに出力することで、簡単に結果を確認する事ができます。
メルカリをスクレイピングしても大丈夫?という疑問に関しては、こちらのサイトで解説されていますので参考にしてください。ここでは問題無しと判断して解説していますが、リスクは承知した上で自己責任での実施をお願いします。
また、メルカリのページ構成は頻繁に変更されますので、サンプルコードの通りに動かしても情報が取得できないこともありますので、ご了承ください。
使用ライブラリ・ドライバ
PythonでWebスクレイピングを行うためのライブラリ Requests, Beautiful Soup,を使用します。
ライブラリの基本的な使い方は以下の記事を参照してください。
・関連記事:【入門】PythonによるWebスクレイピング
さらに、画像取得のためにPillow、Excelを扱うためのopenpyxlを使用します。
・関連記事:PythonでExcelファイルを読み書きする(openpyxl)
メルカリのページをスクレイピングするためには、Selenium というライブラリを使用する必要があります。(Selenium公式ページ)
この Selenium は、Webdriverを仲介してブラウザを操作するため、Webdriverのインストールも必要になります。
ここでは Chrome を利用し、Cドライブ直下にChrome用の webdriver が配置されている前提で解説します。
このドライバは、ブラウザのバージョンに合ったものを使用する必要がありますので、ブラウザのバージョンを確認して導入してください。Chromedriver入手先
ブラウザの自動更新設定が有効の場合、知らぬ間にドライバのバージョンが合わなくなりエラーが出てしまうため、注意してください。
以下のコードで必要なライブラリをまとめてインストールできます。
1 | python -m pip install requests beautifulsoup4 pillow openpyxl selenium |
メルカリ商品情報(タイトル・画像・リンク)を取得してExcelに出力する
メルカリの検索結果から商品情報を取得して、タイトル・サムネイル画像・詳細ページのリンクをExcelに出力するプログラムです。
実行はコマンドプロンプトから行い、引数に検索キーワードを指定します。
※Chromedriver は、ご利用のChromeバージョンに対応したDriverを導入する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | # PRG1:ライブラリ設定 import sys import os import time import shutil import requests from bs4 import BeautifulSoup from selenium import webdriver from selenium.webdriver.chrome.options import Options import io from PIL import Image import openpyxl as px from openpyxl.styles import Alignment # メインプログラム def main(): # PRG2:クロール設定 BASE_URL = 'https://jp.mercari.com' CUR_DIR = os.getcwd() args = sys.argv KEYWORD = args[1] for i in range(2,len(args)): KEYWORD += '+' + args[i] # 検索用URL生成 URL_INI = BASE_URL + '/search?keyword=' + KEYWORD + '&status=on_sale' url = URL_INI page_num = 1 # 検索結果格納リスト・画像保存フォルダ作成 result = [] if os.path.isdir('./img') == False: os.mkdir('./img') # PRG3:スクレイピング実行 while True: try: # ChromeでURLに接続 options = Options() options.add_argument('--headless') browser = webdriver.Chrome(executable_path='C:\chromedriver.exe', options=options) browser.get(url) time.sleep(5) html = browser.page_source.encode('utf-8') # Beautifulsoupで要素取得 soup = BeautifulSoup(html, "html.parser") items_list = soup.find_all("li", attrs={'data-testid':'item-cell'}) # 商品情報取得 for i, item in enumerate(items_list): time.sleep(1) # 商品名取得 item_name = item.find("mer-item-thumbnail").get('alt') item_name = item_name.replace('のサムネイル','') # サムネイル画像取得・加工 img_src = item.find("mer-item-thumbnail").get('src') response = requests.get(img_src) img_fname = img_src.split('?')[0].split('/')[-1] img_bin = io.BytesIO(response.content) pil_img = Image.open(img_bin) img_resize = scale_to_width(pil_img, 200) img_resize.save('./img/' + img_fname) # 取得結果をリストに保存 result.append([item_name, int(item.find("mer-item-thumbnail").get('price')), os.path.join(CUR_DIR, 'img', img_fname), BASE_URL + item.find('a').get('href')]) # 次ページボタン処理 next_button = soup.find('mer-button',attrs={'data-testid':"pagination-next-button"}) if next_button: page_num += 1 param = '&page_token=v1%3A' + str(page_num) next_url = URL_INI + param url = next_url next_url = '' else: break # エラー発生時の例外処理 except Exception as e: message = "[エラー]"+"type:{0}".format(type(e))+"\n"+"args:{0}".format(e.args) sys.stderr.write(message) print("\nPress Enter to continue ...") input() # Chrome終了処理 finally: browser.close() browser.quit() # PRG4:Excelに結果出力 wb = px.Workbook() ws = wb.active # 書式設定 ws.column_dimensions['A'].width = 25 ws.column_dimensions['B'].width = 18 ws.column_dimensions['C'].width = 25 ws.column_dimensions['D'].width = 40 my_alignment=Alignment(vertical='top', wrap_text=True) # Excelヘッダー出力 headers = ['アイテム名','価格','サムネイル','URL'] for i, header in enumerate(headers): ws.cell(row=1, column=i+1, value=header) # 取得結果書き込み for y, row in enumerate(result): ws.row_dimensions[y+2].height = 160 for x, cell in enumerate(row): if x == 2: img = px.drawing.image.Image(cell) img.anchor = ws.cell(row= y+2, column= x+1).coordinate ws.add_image(img) elif x == 3: ws.cell(row= y+2, column= x+1).hyperlink = cell ws.cell(row= y+2, column= x+1).alignment = my_alignment else: ws.cell(row= y+2, column= x+1).value = cell ws.cell(row= y+2, column= x+1).alignment = my_alignment # Excelファイル保存 xlname = './mercari_' + KEYWORD + '.xlsx' wb.save(xlname) # 保存した画像の削除 shutil.rmtree('./img') print('--- END ---') # PRG5:アスペクト比固定して画像リサイズ def scale_to_width(img, width): height = round(img.height * width / img.width) return img.resize((width, height)) # スクリプトとして実行された場合の処理 if __name__ == '__main__': main() |
例として上記のスクリプトのファイル名を「mercari.py」検索キーワードを「python3 入門」で実行してみます。
コマンドプロンプトに以下のように入力します。
1 | >python mercari.py python3 入門 |
以下がExcelに出力された結果です。
D列のURLはハイパーリンクの設定をしているので、クリックで詳細ページに飛ぶことができます。
実行時に次のようなエラーが発生する場合は、Webdriverのディレクトリ指定が間違えていたり、ブラウザバージョンに対応していない為、再度確認してください。
1 2 | [エラー]type:<class 'selenium.common.exceptions.WebDriverException'> ("'chromedriver.exe' executable needs to be in PATH. Please see https://chromedriver.chromium.org/home",) |
Webdriver が導入されていない・ディレクトリ指定が間違っている。
→Webdriverを配置・配置先とスクリプト内の指定が合っているか確認。
1 2 3 | [エラー]type:<class 'selenium.common.exceptions.SessionNotCreatedException'> ('session not created: This version of ChromeDriver only supports Chrome version 101 Current browser version is 106.0.5249.119 with binary path C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', |
Webdriverバージョンがブラウザバージョンに対応していない。
→公式サイトから対応するバージョンをダウンロードして再配置。
ブラウザバージョンは メニュー>ヘルプ>Google Chromeについて から確認
プログラム解説
プログラムを解説します。
プログラムは上から順に説明しますが、スクリプトを実行するとPRG1のライブラリ設定を読み込んだ後に、PRG2, 3 を含む main()
関数が実行されます。
PRG1:ライブラリ設定
1 2 3 4 5 6 7 8 9 10 11 12 13 | # PRG1:ライブラリ設定 import sys import os import time import shutil import requests from bs4 import BeautifulSoup from selenium import webdriver from selenium.webdriver.chrome.options import Options import io from PIL import Image import openpyxl as px from openpyxl.styles import Alignment |
プログラムを解説していきます。
import os
import time
import shutil
コマンドライン引数取得、ファイル・ディレクトリ操作、スリープを行うためのモジュールを読み込みます。
from bs4 import BeautifulSoup
HTMLのダウンロードと解析を行うためにrequests
BeautifulSoup
をインポートします。
インストールや基本的な使い方は以下の記事を参照してください。
・関連記事:【入門】PythonによるWebスクレイピング
from selenium.webdriver.chrome.options import Options
メルカリのページをスクレイピングするためには、Seleniumが必要になるため読み込みます。インストールされていない場合は、python -m pip install selenium
でインストールしてください。
・関連記事:Pythonライブラリのインストール(pipの使い方)
from PIL import Image
画像処理を行うために必要なモジュール、ライブラリを読み込みます。
ここではPillowライブラリを使用しているため、インストールされていない場合は、python -m pip install Pillow
でインストールを行ってください。
・公式ドキュメント:Pillow — Pillow (PIL Fork) 6.1.0 documentation
from openpyxl.styles import Alignment
Excelに取得したデータを出力するためにopenpyxl
ライブラリを使用します。また、書式設定を行うためにAlignment
関数を呼び出しています。
PRG2: クロール設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # PRG2:クロール設定 BASE_URL = 'https://jp.mercari.com' CUR_DIR = os.getcwd() args = sys.argv KEYWORD = args[1] for i in range(2,len(args)): KEYWORD += '+' + args[i] # 検索用URL生成 URL_INI = BASE_URL + '/search/?keyword=' + KEYWORD + '&status=on_sale' url = URL_INI page_num = 1 # 検索結果格納リスト・画像保存フォルダ作成 result = [] if os.path.isdir('./img') == False: os.mkdir('./img') |
クロールを行う際の初期設定を行っています。
クロール時のヘッダーURLを指定します。
画像ファイルを保存するディレクトリを作成するために、スクリプトが実行されているディレクトリを取得します。getcwd()
については以下の記事を参考にしてください。
・参考記事:Pythonでカレントディレクトリを取得・変更
KEYWORD = args[1]
for i in range(2,len(args)):
KEYWORD += ‘+’ + args[i]
プログラム実行時にコマンドラインで指定した引数を読み込み、args
に格納します。args
は、[ファイル名, 引数1, 引数2, ・・・] のように、ファイル名と引数のリストになります。args
からファイル名を除いた引数を+
で結合して、検索キーワードKEYWORD
に格納して、「引数1+引数2+・・・」という文字列を生成します。
・関連記事:【Python入門】argparseでコマンドライン引数を指定して実行
url = URL_INI
ヘッダーURLと検索キーワード、販売中のみを表示するためのURLパラメータ'&status=on_sale'
を結合して、クロールするURLを生成します。
検索結果ページを指定する為の変数をpage_num
として、初期値1を格納します。
result = []
if os.path.isdir(‘./img’) == False:
os.mkdir(‘./img’)
検索した結果のアイテム情報を保存するリストresult
を定義して、画像を保存するディレクトリimg
を作成します。
PRG3: スクレイピング実行
スクレイピング処理をwhile true
でループさせ、break
で中断されるまで繰り返し処理を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | # PRG5スクレイピング実行 while True: try: # ChromeでURLに接続 options = Options() options.add_argument('--headless') browser = webdriver.Chrome(executable_path='C:\chromedriver.exe', options=options) browser.get(url) time.sleep(5) html = browser.page_source.encode('utf-8') # Beautifulsoupで要素取得 soup = BeautifulSoup(html, "html.parser") items_list = soup.find_all("li", attrs={'data-testid':'item-cell'}) # 商品情報取得 for i, item in enumerate(items_list): time.sleep(1) # 商品名取得 item_name = item.find("mer-item-thumbnail").get('alt') item_name = item_name.replace('のサムネイル','') # サムネイル画像取得・加工 img_src = item.find("mer-item-thumbnail").get('src') response = requests.get(img_src) img_fname = img_src.split('?')[0].split('/')[-1] img_bin = io.BytesIO(response.content) pil_img = Image.open(img_bin) img_resize = scale_to_width(pil_img, 200) img_resize.save('./img/' + img_fname) # 取得結果をリストに保存 result.append([item_name, int(item.find("mer-item-thumbnail").get('price')), os.path.join(CUR_DIR, 'img', img_fname), BASE_URL + item.find('a').get('href')]) # 次ページボタン処理 next_button = soup.find('mer-button',attrs={'data-testid':"pagination-next-button"}) if next_button: page_num += 1 param = '&page_token=v1%3A' + str(page_num) next_url = URL_INI + param url = next_url next_url = '' else: break # エラー発生時の例外処理 except Exception as e: message = "[エラー]"+"type:{0}".format(type(e))+"\n"+"args:{0}".format(e.args) sys.stderr.write(message) print("\nPress Enter to continue ...") input() # Chrome終了処理 finally: browser.close() browser.quit() |
プログラムを解説します。
options = Options()
options.add_argument(‘–headless’)
browser = webdriver.Chrome(executable_path=’C:\chromedriver.exe’, options=options)
browser.get(url)
time.sleep(5)
html = browser.page_source.encode(‘utf-8’)
webdriverでクロール対象のページのHTMLを取得します。
options.add_argument('--headless')
とすることでブラウザはバックグラウンドで起動され、非表示にすることができます。ブラウザを表示させたい場合はこの行をコメントアウトします。
ブラウザに結果が表示されるのを待つためにtime.sleep(5)
で5秒間処理を停止させ、取得したHTMLの文字化けを避けるためpage_source.encode('utf-8')
でUTF-8に変換しています。
soup = BeautifulSoup(html, “html.parser”)
items_list = soup.find_all(“li”, attrs={‘data-testid’:’item-cell’})
取得したHTMLをBeautifulSoupで解析します。検索結果は、li
タグの 属性名がdata-testid
値が"item-cell"
であるため、find_all("li", attrs={'data-testid':'item-cell'}
として、タグと属性で要素を検索して、見つかった全ての要素をitem_list
として取得します。
for i, item in enumerate(items_list):
time.sleep(1)
取得したアイテムリストからアイテムを一つずつ取り出して、1秒間休止して必要な情報を抽出します。
item_name = item.find(“mer-item-thumbnail”).get(‘alt’)
item_name = item_name.replace(‘のサムネイル’,”)
商品名は、サムネイルの alt
属性から取得したテキストを使用しています。
属性名は、商品名 +「のサムネイル」となっているため、「のサムネイル」を replace
で削除(''
に置換)しています。
img_src = item.find(“mer-item-thumbnail”).get(‘src’)
response = requests.get(img_src)
img_fname = img_src.split(‘?’)[0].split(‘/’)[-1]
img_bin = io.BytesIO(response.content)
pil_img = Image.open(img_bin)
img_resize = scale_to_width(pil_img, 200)
img_resize.save(‘./img/’ + img_fname)
サムネイル画像のソースURLをimg_src
に格納し、requests
ライブラリで画像を取得します。
取得した画像データは、io.BytesIO
でバイナリデータのままPillow
ライブラリに渡し、後述するユーザ関数scale_to_width
で、画像の幅を指定してアスペクト比を変えずにサイズ変更を行っています。
最後に、サイズ変更した画像をimg
ディレクトリに保存します。
result.append([item_name,
int(item.find(“mer-item-thumbnail”).get(‘price’)),
os.path.join(CUR_DIR, ‘img’, img_fname),
BASE_URL + item.find(‘a’).get(‘href’)])
商品名、価格、画像ファイルパス、詳細ページURLをresult
にリスト内リストで格納します。
next_button = soup.find(‘mer-button’,attrs={‘data-testid’:”pagination-next-button”})
if next_button:
page_num += 1
param = ‘&page_token=v1%3A’ + str(page_num)
next_url = URL_INI + param
url = next_url
next_url = ”
else:
break
検索結果が1ページで収まる場合は次ページボタンは表示されず、複数ページになる場合は、次ページボタンが表示されます。
これを利用して、次ページ要素がある場合は、次ページのURLをクロールURLに設定してスクレイピング処理を継続し、要素が無い場合はbreak
でループ処理を終了させます。
検索結果の2ページ目以降は、URLパラメータが「&page_token=v1%3A+ページ番号」で表現されているため、page_num
を1ずつ増加させてヘッダURLに追加してクロールURLに設定しています。
PRG4:Excelに結果出力
取得した検索結果の情報をExcelに出力して、一覧リストとして整理します。
Excelの操作方法についてはこちらの記事を参考にしてください。
・参考記事:【Python入門】openpyxlによるExcel操作まとめ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | # PRG4:Excelに結果出力 wb = px.Workbook() ws = wb.active # Excel書式設定 ws.column_dimensions['A'].width = 25 ws.column_dimensions['B'].width = 18 ws.column_dimensions['C'].width = 25 ws.column_dimensions['D'].width = 40 my_alignment=Alignment(vertical='top', wrap_text=True) # Excelヘッダー出力 headers = ['アイテム名','価格','サムネイル','URL'] for i, header in enumerate(headers): ws.cell(row=1, column=i+1, value=header) # 取得結果書き込み for y, row in enumerate(result): ws.row_dimensions[y+2].height = 160 for x, cell in enumerate(row): # 画像 if x == 2: img = px.drawing.image.Image(cell) img.anchor = ws.cell(row= y+2, column= x+1).coordinate ws.add_image(img) # URLリンク elif x == 3: ws.cell(row= y+2, column= x+1).hyperlink = cell ws.cell(row= y+2, column= x+1).alignment = my_alignment # タイトル、価格 else: ws.cell(row= y+2, column= x+1).value = cell ws.cell(row= y+2, column= x+1).alignment = my_alignment # Excelファイル保存 xlname = './mercari_' + KEYWORD + '.xlsx' wb.save(xlname) |
ws.column_dimensions[‘A’].width = 25
ws.column_dimensions[‘B’].width = 18
ws.column_dimensions[‘C’].width = 25
ws.column_dimensions[‘D’].width = 40
my_alignment=Alignment(vertical=’top’, wrap_text=True)
出力した結果を見やすくするために、列の幅を調整します。また、文字の上揃えとテキストの折返しの設定を定義します。
for y, row in enumerate(result):
ws.row_dimensions[y+2].height = 160
for x, cell in enumerate(row):
# 画像
if x == 2:
img = px.drawing.image.Image(cell)
img.anchor = ws.cell(row= y+2, column= x+1).coordinate
ws.add_image(img)
# URLリンク
elif x == 3:
ws.cell(row= y+2, column= x+1).hyperlink = cell
ws.cell(row= y+2, column= x+1).alignment = my_alignment
# タイトル、価格
else:
ws.cell(row= y+2, column= x+1).value = cell
ws.cell(row= y+2, column= x+1).alignment = my_alignment
result
に格納されているアイテム情報をExcelに出力していきます。
・・関連記事:PythonでExcelファイルを読み書きする(openpyxl)
画像をExcelに貼り付けには、画像をopenpyxl
のdrawing.image
モジュールを使用して読み込みます。
画像の貼り付け位置を指定するための.anchor
プロパティには、A1参照形式を指定しないとエラーになるため、R1C1参照形式のcoordinate
属性を取得しています。
PRG5:アスペクト比を固定して画像リサイズ
1 2 3 4 | # PRG5:アスペクト比を固定して画像リサイズ def scale_to_width(img, width): height = round(img.height * width / img.width) return img.resize((width, height)) |
Pillowの resize
メソッドでは、アスペクト比を固定できないため、画像サイズを幅に合わせて変更する関数を用意します。
width(変更後の幅) / img.width(元の幅) で、サイズ変更の比率を算出して、高さにその比率を掛けて変更後の高さを計算しています。
return
でサイズ変更した画像を、戻り値として返します。
以上でプログラムの解説を終わります。
定期的に自動実行する場合は、以下の記事を参考にしてください。
・関連記事:【Win11対応】Pythonスクリプトを自動実行する方法