プログラムの力を使えば、時間のかかる面倒な作業も自動で処理できる。私自身、ネットショップの運営に携わったことがあり、その時は人手が足らず全てが後手で大変だった。
今はプログラムが書けるので、定期処理などは自動化できる。
ネットショップのバックオフィス業務で、自動化できることの一つに競合チェックがある。楽天では(というか大抵のモールでは)、同一商品の最安価格が簡単にわかる機能がある。
利用者はワンクリックで商品の安い順で一覧を見ることができる。当然ネットショップとしては、お客さんの目につく場所に自分の店が表示されるように、最安価格を維持したいわけだ。
かくしてネットショップ店長やら従業員は、人手をかけて商品の最安価格をリストアップするのだが、こういったことは今やプログラムで簡単に自動処理できる。
また、最安価格以外にも、競合店舗がどんな商品を扱っているのか、その店舗の取り扱い商品を一覧にして、商品名、価格、画像などをデータセットにしたいという要望もあるだろう。
人手をかけるよりプログラムにやらせる
これも人手をかけてする場合は、商品一覧から商品詳細ページにクリックで遷移し、1ページずつ商品名や価格をコピペするという気の遠くなるような作業となる。商品数が多い場合は真面目にやっているとバカみたいに時間がかかる。中には短期のアルバイトを雇ったり、クラウドソーシングで作業依頼したりすることもあるだろう。
しかし、これもプログラムの力を使えば、ネットショップ店長が寝ている間にコンピュータが勝手に情報を収集してくれるからありがたい。
インターネットに公開されている情報を収集する方法を一般に「スクレイピング」という。人がブラウザを操作するのではなく、コンピュータに自動でブラウザを操作させるような感じだ。アクセスしたページの特定の情報だけを抜き出してエクセルやスプレッドシートにするなんてこともできる。
以下に楽天の指定の店舗から商品一覧を生成するサンプルのソースコードを掲載する。もちろんサーバーの過負荷にならない配慮をしている。
import mechanicalsoup
import requests
import time
import datetime
import selenium
import random
from bs4 import BeautifulSoup
import os
import sys
import re
from requests.exceptions import Timeout
import csv
today = str(datetime.date.today())
class make_promotion_list:
"""
店舗リスト作成用のスクレイプ
"""
def __init__(self, url):
self.base_url = url
self.shopcode = None
self.master_list = []
self.item_page_list = []
def simple_request(self, url, first):
"""
指定URLから一意なaタグをリスト化
first:初回リクエストかどうか bool
"""
print(url, 'を実行 ', len(self.master_list), '個のリスト')
atag_list = []
#urlにshopcodeが含まれない場合は対象外とする
if self.shopcode not in url:
return False
try:
r = requests.get(url, timeout=(30.0, 47.5))
except (requests.exceptions.MissingSchema) as e:
return False
if r.status_code == 200 and 'text/html' in r.headers['Content-type']:
soup = BeautifulSoup(r.content, 'html.parser')
inquiry_anchor_list = soup.find_all('a')
if first == True: #初回リクエスト
for row in inquiry_anchor_list:
if row.get('href') not in self.master_list:
self.master_list.append(row.get('href'))
if row.get('href') not in atag_list:
atag_list.append(row.get('href'))
return atag_list
elif first == False: #2回目以降のリクエストはmasterに追加するだけ
for row in inquiry_anchor_list:
if row.get('href') not in self.master_list:
self.master_list.append(row.get('href'))
return len(self.master_list)
def get_rakuten_shopcode(self, url):
"""
urlから楽天のshopcodeを取得
https://www.rakuten.co.jp/shopcode/
"""
pattern = 'https://www.rakuten.co.jp/.+/'
if re.match(pattern, url) != None:
self.shopcode = url[26:].replace('/', '')
return self.shopcode
def get_item_url(self):
"""
完成したmaster_listから個別商品のurlのみをピックアップ
https://item.rakuten.co.jp/shopcode/10000004/
楽天の場合はこういうurl
"""
pattern = 'https://item.rakuten.co.jp/[a-z\-]+/[0-9]+/'
for row in self.master_list:
try:
if re.match(pattern, row) != None:
self.item_page_list.append(row)
except TypeError:
pass
return self.item_page_list
def get_item_info(self, url):
"""
楽天の詳細ページから商品情報を取得
"""
try:
r = requests.get(url, timeout=(30.0, 47.5))
print(r.status_code)
except (requests.exceptions.MissingSchema) as e:
return False
i = 0
while r.status_code == 503:
try:
r = requests.get(url, timeout=(30.0, 47.5))
print(r.status_code)
except (Timeout, requests.exceptions.MissingSchema) as e:
print('timeoutで取得できず')
time.sleep(10)
i += 1
if i > 5: #5回503が続いたら次にいく
break
if r.status_code == 200:
break
tmp_item_data = {}
if r.status_code == 200 and 'text/html' in r.headers['Content-type']:
soup = BeautifulSoup(r.content, 'html.parser')
item_name = soup.select_one('.item_name')
item_price = soup.select_one('.price2')
tmp_item_data['url'] = url
item_number_pattern = '[0-9]+'
match_result = re.search(item_number_pattern, url)
if match_result != None:
tmp_item_data['item_id'] = match_result.group()
else:
tmp_item_data['item_id'] = None
"""
try:
tmp_item_data['item_id'] = item_id.text
except AttributeError:
tmp_item_data['item_id'] = None
"""
try:
tmp_item_data['item_name'] = item_name.text
except AttributeError:
tmp_item_data['item_name'] = None
try:
tmp_item_data['item_price'] = item_price.text
except AttributeError:
tmp_item_data['item_price'] = None
elif r.status_code == 503:
tmp_item_data['url'] = url
tmp_item_data['item_id'] = "楽天のサーバー遅延などで取得できず"
tmp_item_data['item_name'] = None
tmp_item_data['item_price'] = None
return tmp_item_data
if __name__ == "__main__":
while True:
print('調査したい楽天店舗のショップトップページを入力してください')
url = input('url:')
#urlのvalidation
pattern = 'https://www.rakuten.co.jp/[a-z\-]+/?'
result = re.match(pattern, url)
if result == None:
print('urlが不正です。https://www.rakuten.co.jp/shopcode/ のように入力してください')
elif result != None:
print('スクレイピングを開始します。商品数によっては数時間〜1日かかることもあります。')
break
request_instance = make_promotion_list(url)
shopcode = request_instance.get_rakuten_shopcode(url)
#初回リクエストでリンクリスト作成
url_list = request_instance.simple_request(url, True)
if url_list == False:
print('処理を終了')
exit()
#2回目以降
for url in url_list:
request_instance.simple_request(url, False)
session_interval_time = random.uniform(1, 1.7)
time.sleep(session_interval_time) #何秒おきにリクエストするか1〜1.7秒の中でランダムで設定
else:
count = request_instance.simple_request(url, False)
print('数:', count)
#アイテムページのみをピックアップ
item_page_list = request_instance.get_item_url()
print('アイテム数: ', len(item_page_list))
#詳細ページurlから商品情報を取得
item_list = []
for row in item_page_list:
print(row, 'を処理')
item_list.append(request_instance.get_item_info(row))
time.sleep(1)
#csv保存
filepath = "./csv_data/"
filename = today + '-' + str(shopcode) + '.csv'
with open(filepath + filename, 'w') as f:
writer = csv.writer(f)
for row in item_list:
writer.writerow([row['url'], row['item_id'], row['item_name'], row['item_price']])

