巷では、Webブラウザを制御して、色々と情報を取得するのが流行っているみたいです。
そう、スクレイピングですね
ちょっと、ダーティーな感じもしますが、元々、Webのテスト用ツールだったみたいで、本来そういった使い方をしてほしいんですかね
まー、そういうのは、どうでも良いんですが、Webの情報って、Htmlを解析して、ここのエレメントから属性取ってとかってしないといけないので、面倒です
昔から色々、そういったツールがあったんですが、最終的にSeleniumが生き残ったみたいです。
Seleniumは、C#で適当に組めば、動作させることが可能なんですが、環境構築が面倒です。
なので、今回は、Dockerを使って、やってみたいと思います。
ところで、アマテンってご存知でしょうか。
ネットのギフト券ショップです。
扱っているのは、コードで販売出来る金券だけですけど
アマゾンギフト券とか、GooglePlayカードとかですね
結構、割引が大きい時があるのですが、常に見張っておかないと値動きがあるので、良いタイミングを逃してしまいます。
そこで、値動きを監視するようなツールをnodejs(JavaScript)を使って作ってみましょう。
Dockerfile
FROM ubuntu
EXPOSE 8088
RUN apt -y update
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Tokyo
RUN apt install -y tzdata
RUN apt install -y nodejs npm
RUN apt install -y curl
RUN curl -LO https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
RUN apt-get install -y ./google-chrome-stable_current_amd64.deb
RUN rm google-chrome-stable_current_amd64.deb
RUN apt install -y fonts-noto
WORKDIR /project
docker-compose.yml
version: "3"
services:
scrape1:
build:
context: .
ports:
- "8088:8088"
expose:
- "8088"
volumes:
- ./project:/project
tty: true
working_dir: /project
command: bash /project/init.sh
deploy:
resources:
limits:
memory: 256m
amaten.js
const puppeteer = require('puppeteer');
let browser = null;
/*
AmatenよりAmazonギフト券価格を取得
*/
async function amazonprice() {
if( browser == null )
browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
const page = await browser.newPage();
await page.setViewport({width:1280,height:1024});
await page.goto('https://amaten.com/exhibitions/amazon');
await page.waitForTimeout(1000);
const amazonpricelistelem = await page.$x('//*[@id="contents_list"]/div[4]/table/tbody');
const listelem = await amazonpricelistelem[0].$x("tr");
var list = new Array();
for( let i=0; i<listelem.length; i++) {
var record = {};
var giftcount = await(await (await listelem[i].$x('th/span[@class="js-gift-count"]'))[0].getProperty('textContent')).jsonValue();
var facevalue = await(await (await listelem[i].$x('td/span[@class="js-face_value"]'))[0].getProperty('textContent')).jsonValue();
var pricevalue = await(await (await listelem[i].$x('td/div/span[@class="js-price"]'))[0].getProperty('textContent')).jsonValue();
var rate = await(await (await listelem[i].$x('td/span[@class="js-rate"]'))[0].getProperty('textContent')).jsonValue();
record["giftcount"] = giftcount;
record["facevalue"] = facevalue;
record["pricevalue"] = pricevalue;
record["rate"] = rate;
list.push(record);
}
await page.close();
return list;
}
// アマテン価格出力文字列
var pricecontents = "お待ち下さい\n";
/*
アマテン価格出力文字列作成
*/
function coockedprice( pricelist ) {
var now = new Date();
pricecontents = `${now.getFullYear()}年${now.getMonth()+1}月${now.getDate()}日${now.getHours()}時${now.getMinutes()}分${now.getSeconds()}秒`+"\n";
for( var idx=0; idx<pricelist.length; idx++) {
var record = pricelist[idx];
pricecontents += `${record["rate"]}% ${record["giftcount"]}個 ${record["facevalue"]}円 → ${record["pricevalue"]}円`+"\n";
if( idx>=5) {
break;
}
}
}
/*
アマテン価格出力文字列作成タイマー
*/
setInterval(function(){
amazonprice().then( val => coockedprice(val) );
},180000);
/*
Webサーバー起動
*/
function startserver() {
const http = require('http');
const hostname = '0.0.0.0';
const port = 8088;
const server = http.createServer((req, res) => {
if( req.method == "GET") {
switch(req.url) {
case "/amaten":
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
//amaten価格応答
res.end(pricecontents);
break;
default:
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end("OK");
break;
}
}
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
}
//Webサーバー開始
startserver();
init.sh
#/bin/bash
if [ ! -e node_modules ]; then
npm install puppeteer
fi
npm start
package.json
{
"name": "amaten1",
"version": "1.0.0",
"description": "Amaten scraper",
"main": "amaten.js",
"directories": {
"lib": "lib"
},
"dependencies": {
"puppeteer": "^10.4.0"
},
"scripts": {
"start": "node amaten.js"
},
"author": "DangerousWOO",
"license": "ISC"
}
フォルダ構成
project
├Dockerfile
├docker-compose.yml
└project
├init.sh
├amaten.js
└package.json
とまあ、こんな感じで作ってみました。
nodejsなんて、普段使わないので、ちょっと疲れました。
これは、seleniumじゃなくて、puppeteerというツールを使ってみました。
puppeteerの詳細は、ググって調べて下さいね
とりあえず、Chromeを起動して、ページ開いて、アマテンから価格情報を取って来ています。
Webサーバも起動していて、http://localhost:8088/amaten で、価格情報が取れるようになっています。
価格情報は、3分間隔で更新しています。
起動方法は、以下です。
docker-compose up -d
最初だけ、イメージを構築するので遅いです。
次にpython3版を作ってみましょう。
Dockerfile
FROM ubuntuEXPOSE 8088
RUN apt -y update
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Tokyo
RUN apt install -y tzdata
RUN apt install -y curl python3 python3-pip
RUN pip install --upgrade pip
RUN curl -LO https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
RUN apt-get install -y ./google-chrome-stable_current_amd64.deb
RUN rm google-chrome-stable_current_amd64.deb
RUN apt install -y fonts-noto
RUN apt install -y python3-selenium unzip
WORKDIR /project
docker-compose.yml
version: "3"
services:
scrapepy1:
build:
context: .
ports:
- "8088:8088"
expose:
- "8088"
volumes:
- ./project:/project
tty: true
working_dir: /project
command: bash /project/init.sh
deploy:
resources:
limits:
memory: 256m
scrape.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import chromedriver_binary
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import datetime
from http.server import HTTPServer, SimpleHTTPRequestHandler
from concurrent.futures import ThreadPoolExecutor
'''
AmatenよりAmazonギフト券価格を取得
'''
def amazonprice():
options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--disable-setuid-sandbox')
options.add_argument('--window-size=1280x1024')
driver = webdriver.Chrome(chrome_options=options)
driver.get("https://amaten.com/exhibitions/amazon")
WebDriverWait(driver,10).until(EC.presence_of_all_elements_located)
time.sleep(1)
list = []
for elem_tr in driver.find_elements_by_xpath('//*[@id="contents_list"]/div[4]/table/tbody/tr'):
record = {}
giftcount = elem_tr.find_element_by_xpath('th/span[@class="js-gift-count"]')
facevalue = elem_tr.find_element_by_xpath('td/span[@class="js-face_value"]')
pricevalue = elem_tr.find_element_by_xpath('td/div/span[@class="js-price"]')
rate = elem_tr.find_element_by_xpath('td/span[@class="js-rate"]')
record["giftcount"] = giftcount.text
record["facevalue"] = facevalue.text
record["pricevalue"] = pricevalue.text
record["rate"] = rate.text
list.append(record)
if len(list) > 5 :
break
driver.close()
driver.quit()
return list
'''
アマテン価格応答文字列
'''
content = 'お待ち下さい\n'
'''
Webサーバー起動
'''
class MyHandler(SimpleHTTPRequestHandler):
def do_GET(self) -> None:
body = ''
if( self.path == '/amaten') :
#amaten価格応答
body = content.encode('utf-8')
self.send_response(200)
self.send_header('Content-type', 'text/application; charset=utf-8')
self.send_header('Content-length',len(body))
self.end_headers()
self.wfile.write(body)
def StartWeb():
host = '0.0.0.0'
port = 8088
httpd = HTTPServer((host, port), MyHandler)
print('serving at port', port)
httpd.serve_forever()
'''
アマテン価格応答文字列構築
'''
def coockedprice():
global content
dt_now = datetime.datetime.now()
pricecontents = dt_now.strftime('%Y年%m月%d日 %H時%M分%S秒\n')
for record in amazonprice():
pricecontents += "{0}% {1}個 {2}円 → {3}円\n".format(record["rate"],record["giftcount"],record["facevalue"],record["pricevalue"])
content = pricecontents
'''
メイン起動ループ
'''
with ThreadPoolExecutor(max_workers=2) as executor:
#Webサーバスレッド開始
executor.submit(StartWeb)
#アマテン応答文字列構築ループ開始
while True:
executor.submit(coockedprice)
time.sleep(60)
init.sh
#/bin/bash
webdriverexist=`pip list | grep chromedriver-binary | wc -l`
if [ $webdriverexist -eq 0 ]; then
google-chrome --version
#https://chromedriver.chromium.org/downloads
pip install chromedriver-binary==94.0.4606.41
fi
python3 scrape.py
フォルダ構成
project
├Dockerfile
├docker-compose.yml
└project
├init.sh
└scrape.py
Python3版も完成です。
Python3も普段使わないので、疲れますな
こっちは、ちゃんとSeleniumを使いましたよ
でも、WebDriverとChromeの互換性を一致させる必要があるので、init.shを修正する必要がありますね。
現代のプログラムって、どの言語も根源的な物は、ノイマン型コンピューターなんで、同じ様なもんですな
量子コンピューターだと、どうな風になるのかね