巷では、Webブラウザを制御して、色々と情報を取得するのが流行っているみたいです。
そう、スクレイピングですね
ちょっと、ダーティーな感じもしますが、元々、Webのテスト用ツールだったみたいで、本来そういった使い方をしてほしいんですかね
まー、そういうのは、どうでも良いんですが、Webの情報って、Htmlを解析して、ここのエレメントから属性取ってとかってしないといけないので、面倒です
昔から色々、そういったツールがあったんですが、最終的にSeleniumが生き残ったみたいです。
Seleniumは、C#で適当に組めば、動作させることが可能なんですが、環境構築が面倒です。
なので、今回は、Dockerを使って、やってみたいと思います。
ところで、アマテンってご存知でしょうか。
ネットのギフト券ショップです。
扱っているのは、コードで販売出来る金券だけですけど
アマゾンギフト券とか、GooglePlayカードとかですね
結構、割引が大きい時があるのですが、常に見張っておかないと値動きがあるので、良いタイミングを逃してしまいます。
そこで、値動きを監視するようなツールをnodejs(JavaScript)を使って作ってみましょう。
Dockerfile
FROM ubuntuEXPOSE 8088RUN apt -y updateENV DEBIAN_FRONTEND=noninteractiveENV TZ=Asia/TokyoRUN apt install -y tzdataRUN apt install -y nodejs npmRUN apt install -y curlRUN curl -LO https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.debRUN apt-get install -y ./google-chrome-stable_current_amd64.debRUN rm google-chrome-stable_current_amd64.deb RUN apt install -y fonts-notoWORKDIR /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 puppeteerfi
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 8088RUN apt -y updateENV DEBIAN_FRONTEND=noninteractiveENV TZ=Asia/TokyoRUN apt install -y tzdataRUN apt install -y curl python3 python3-pipRUN pip install --upgrade pipRUN curl -LO https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.debRUN apt-get install -y ./google-chrome-stable_current_amd64.debRUN rm google-chrome-stable_current_amd64.deb RUN apt install -y fonts-notoRUN apt install -y python3-selenium unzipWORKDIR /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 webdriverfrom selenium.webdriver.chrome.options import Optionsimport chromedriver_binaryfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.webdriver import WebDriverfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECimport timeimport datetimefrom http.server import HTTPServer, SimpleHTTPRequestHandlerfrom 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.41fi
python3 scrape.py
フォルダ構成
project
├Dockerfile
├docker-compose.yml
└project
├init.sh
└scrape.py
Python3版も完成です。
Python3も普段使わないので、疲れますな
こっちは、ちゃんとSeleniumを使いましたよ
でも、WebDriverとChromeの互換性を一致させる必要があるので、init.shを修正する必要がありますね。
現代のプログラムって、どの言語も根源的な物は、ノイマン型コンピューターなんで、同じ様なもんですな
量子コンピューターだと、どうな風になるのかね





