bs4でログイン後の情報をスクレイピングする

まず、login.phpで、sessionのusernameとpasswordがokなら、メールトップ画面にリダイレクトする処理を書いています。

<?php elseif($status == "ok"): header('Location: mail.php?path=u0'); ?> 

こちらがログイン後の画面。outlookのUIを参考にしています。

「yumeさんのメールボックス」がh2です。

続いて、python。 sessionのusernameをyumeでpostして、beautifulsoupでh2をselect_oneします。

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

USER = "yume"
PASS = "hogehoge"

session = requests.session()

login_info = {
	"username" : USER,
	"password": PASS,
}
url_login = "http://open-box.co/login.php"
res = session.post(url_login, data=login_info)
res.raise_for_status()
print("success")

soup = BeautifulSoup(res.text, "html.parser")
a = soup.select_one("h2").string
if a is None:
	print("取得できませんでした")
	quit()
print(a)

[vagrant@localhost python]$ python3 app.py
success
yumeさんのメールボックス

おいおいおい。まてまてまて、頭が追い付かない。
すげー、python!

え、それなら、もしかして、twitterもいける?

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

USER = "hoge"
PASS = "hogehoge"

session = requests.session()

login_info = {
	"signin-email" : USER,
	"signin-password": PASS,
}
url_login = "https://twitter.com/login"
res = session.post(url_login, data=login_info)
res.raise_for_status()
print("success")

[vagrant@localhost python]$ python3 app.py
Traceback (most recent call last):
File “app.py”, line 16, in
res.raise_for_status()
File “/home/vagrant/.pyenv/versions/3.5.2/lib/python3.5/site-packages/requests/models.py”, line 939, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://twitter.com/login

Forbiddenだ。さすがにあかんか。
twitterに限らず、requests.session()では、ログインできないようにできるらしいですね。
なるほどね。

pythonからusernameとpasswordのsessionをpostしてログインする

よく、不正ログインの防ぐために、画像認証などありますが、どうやってプログラムからログインしてるのか、やっとわかりました。
まず、以前自作したメールボックス
http://open-box.co/login.php

phpで、username, passwordをmysqlと照合してcheckしています。

public function login($username, $password){
		$stmt = $this->mysqli->prepare(
			"SELECT password FROM users WHERE username = ?");
		$stmt->bind_param('s', $_POST["username"]);
		$stmt->execute();

		$stmt->store_result();
		if($stmt->num_rows == 1){
			$stmt->bind_result($hash);
			while ($stmt->fetch()){
				if(password_verify($_POST['password'], $hash)){
					$_SESSION["username"] = $_POST["username"];
					return true;
				}
			}
		}
		return false;
	}

pythonでusernameとpasswordのsessionをpostします。

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

USER = "hoge"
PASS = "hogehoge"

session = requests.session()

login_info = {
	"username" : USER,
	"password": PASS,
}
url_login = "http://open-box.co/login.php"
res = session.post(url_login, data=login_info)
res.raise_for_status()
print("success")

エラーだと、res.raise_for_status()でエラーが表示される筈ですが、、、
来た!!!!!
[vagrant@localhost python]$ python3 app.py
success

bs4も使いたい。

SyntaxError: invalid syntax

しょっぱな躓いた。

from bs4 import BeautifulSoup
from urllib.request import
from urllib.parse import
from os import makedirs
import os.path, time, re

proc_files = {}

def enum_links(html, base):
	soup = BeautifulSoup(html, "html.parser")
	links = soup.select("link[rel='stylesheet']")
	links += soup.select("a[href]")
	result = []
	for a in links:
		href = a.attrs['href']
		url = urljoin(base, href)
		result.append(url)
	return result

def download_file(url):
	o = urlparse(url)
	savepath = "./" + o.netloc + o.path
	if re.search(r"/$", savepath):
		savepath += "index.html"
	savedir = os.path.dirname(savepath)
	if os.path.exists(savepath): return savepath
	if not os.path.exists(savedir):
		print("mkdir=", savedir)
		makedirs(savedir)
	try:
		print("download=", url)
		urlretrive(url, savepath)
		time.sleep(1)
		return savepath
	except:
		print("ダウンロード失敗:", url)
		return None

def analize_html(url, root_url):
	savepath = download_file(url)
	if savepath is None: return
	if savepath in proc_files: return
	proc_files[savepath] = True
	print("analize_html=", url)
	html = open(savepath, "r", encoding="utf-8").read()
	links = enum_links(html, url)
	for link_url in links:
		if link_url.find(root_url) != 0:
			if not re.search(r".css$", link_url): continue
		if re.search(r".(html|html)$", link_url):
			analize_html(link_url, root_url)
			continue
		download_file(link_url)

if __name__ == "__main__":
	url = "http://docs.python.jp/3.5/library"
	analize=html(url, url)

はい?
[vagrant@localhost python]$ python3 app.py
File “app.py”, line 2
from urllib.request import
^
SyntaxError: invalid syntax

≧urllib.request モジュールは基本的な認証、暗号化認証、リダイレクション、Cookie、その他の介在する複雑なアクセス環境において (大抵は HTTP で) URL を開くための関数とクラスを定義します。

from urllib.request importって書き方がおかしい気がするが。。

urllib.parse.urljoin()

base urlの相対パスをurljoinで結合していく。

from urllib.parse import urljoin

base = "http://hoge.com/html/crm.html"

print( urljoin(base, "erp.html"))
print( urljoin(base, "accounting/ifrs.html"))
print( urljoin(base, "../scm.html"))
print( urljoin(base, "../img/bi.png"))
print( urljoin(base, "../css/style.css"))

[vagrant@localhost python]$ python3 app.py
http://hoge.com/html/erp.html
http://hoge.com/html/accounting/ifrs.html
http://hoge.com/scm.html
http://hoge.com/img/bi.png
http://hoge.com/css/style.css

もし、urljoinがhttpの場合は、そのまま絶対パスを返す。

from urllib.parse import urljoin

base = "http://hoge.com/html/crm.html"

print( urljoin(base, "erp.html"))
print( urljoin(base, "http://www.nttdata.com/jp/ja/services/oss/index.html"))
print( urljoin(base, "../scm.html"))

[vagrant@localhost python]$ python3 app.py
http://hoge.com/html/erp.html
http://www.nttdata.com/jp/ja/services/oss/index.html
http://hoge.com/scm.html

なるほど。

find_allと正規表現を組み合わせる

urlにolympicが入っているか、regular expressionで検索します。

from bs4 import BeautifulSoup
import re #正規表現を使用

html = """
<ul>
	<li><a href="https://tokyo2020.org/jp/">東京オリンピック</a></li>
	<li><a href="https://www.joc.or.jp/games/olympic/">日本オリンピック委員会</a></li>
	<li><a href="https://www.2020games.metro.tokyo.jp/taikaijyunbi/olympic/index.html">東京都オリンピック・パラリンピック準備局</a></li>
	<li><a href="https://www.asahi.com/olympics/">朝日新聞デジタル 2020東京オリンピック</a></li>
</ul>
"""
soup = BeautifulSoup(html, "html.parser")
li = soup.find_all(href=re.compile(r"olympic"))
for e in li:print(e.string)

[vagrant@localhost python]$ python3 app.py
日本オリンピック委員会
東京都オリンピック・パラリンピック準備局
朝日新聞デジタル 2020東京オリンピック

print(e.attrs[‘href’])とすると、href属性を取得します。
[vagrant@localhost python]$ python3 app.py
https://www.joc.or.jp/games/olympic/
https://www.2020games.metro.tokyo.jp/taikaijyunbi/olympic/index.html
https://www.asahi.com/olympics/

soup.find(“li”)

findメソッドを使って書くこともできる。

cond = {"data-lo":"android", "class":"free"}
print(soup.find("li", cond).string)

print(soup.find(id="popular")
			.find("li", cond).string)

[vagrant@localhost python]$ python3 app.py
Huawei Technologies P20 liteANE-LX2J
HUAWEI HUAWEI P20 lite

bs4でid, classを駆使していく

まず、スマホランキングのhtmlをつくります。しかし、huawei売れてるな~ 周りでも増えてきました。

<html><body>
<div id="smartphone" role="ranking">
	<h1>セールス・人気ランキング</h1>
	<ul id="sales">
		<li class="docomo" data-lo="ios">iPhone 8 64GB(NTT docomo)</li>
		<li class="au" data-lo="ios">iPhone 8 64GB(au)</li>
		<li class="softbank" data-lo="ios">iPhone 8 64GB(SoftBank)</li>
		<li class="free" data-lo="android">Huawei Technologies P20 liteANE-LX2J</li>
		<li class="free" data-lo="android">Huawei Technologies nova lite 2nova lite 2</li>
	</ul>
	<ul id="popular">
		<li class="free" data-lo="android">HUAWEI HUAWEI P20 lite</li>
		<li class="docomo" data-lo="android">Xperia XZ2 Premium SO-04K</li>
		<li class="docomo" data-lo="android">HUAWEI HUAWEI P20 Pro HW-01K</li>
		<li class="free" data-lo="android">HUAWEI HUAWEI P20</li>
		<li class="free" data-lo="android">ASUS ZenFone 4 Max</li>
	</ul>
</div>
</body></html>

続いてpython
id、classだけでなくdata-loを使っています。

from bs4 import BeautifulSoup
fp = open("smartphone.html", encoding="utf-8")
soup = BeautifulSoup(fp, "html.parser")

print(soup.select_one("li:nth-of-type(8)").string)
print(soup.select_one("#popular > li:nth-of-type(3)").string)
print(soup.select("#sales > li[data-lo='android']")[1].string)
print(soup.select("#popular > li.docomo")[0].string)

なるほど。
[vagrant@localhost python]$ python3 app.py
HUAWEI HUAWEI P20 Pro HW-01K
HUAWEI HUAWEI P20 Pro HW-01K
Huawei Technologies nova lite 2nova lite 2
Xperia XZ2 Premium SO-04K

cssセレクタを抽出

dj.htmlを作ります。djの世界ランキング。ulli要素のみ。

<ul id="dj">
	<li id="mg">Martin Carrix</li>
	<li id="dv">Dimitri Vegas & Like Mike</li>
	<li id="ab">Armin van Buuren</li>
	<li id="hd">Hardwell</li>
	<li id="tj">Tjesto</li>
	<li id="cs">The Chainsmokers</li>
	<li id="dg">David Guetta</li>
	<li id="aj">Afrojack</li>
	<li id="sa">Steve Aoki</li>
	<li id="mm">Marshmello</li>
</ul>

続いて、python

from bs4 import BeautifulSoup
fp = open("dj.html", encoding="utf-8")
soup = BeautifulSoup(fp, "html.parser")

sel = lambda q : print(soup.select_one(q).string)
sel("#mm")
sel("li#sa")
sel("ul > li#aj")
sel("#dj #dg")
sel("#dj > #cs")
sel("ul#dj > li#tj")
sel("li[id='hd']")
sel("li:nth-of-type(2)")

print(soup.select("li")[1].string)
print(soup.find_all("li")[0].string)

[vagrant@localhost python]$ python3 app.py
Marshmello
Steve Aoki
Afrojack
David Guetta
The Chainsmokers
Tjesto
Hardwell
Dimitri Vegas & Like Mike
Dimitri Vegas & Like Mike
Martin Carrix

sel(“li:nth-of-type(2)”)と、print(soup.select(“li”)[1].string)の値が同じなのがわかります。配列だと、普通0から始まりますが、nth-of-type(n)は1からのようですね。

>ラムダ式(lambda)を用いて名前のない関数を書けます。名前のない関数を 無名関数 と呼びます。名前が必要ない小さな関数を作るための機能です。
>ラムダ式は以下のように書きます。引数と処理をコロン(:)で区切って書きます。引数が複数ある場合は、通常の関数と同じように感まで区切ります。
lambda q :が無名関数です。
sel = lambda a : print(soup.select_one(a).string) と書いても、同じように動きます。

太宰治の作品

青空文庫のページが単純なli要素の為、

  • 校長三代 (旧字旧仮名、作品ID:43313) 
  • 故郷 (新字新仮名、作品ID:1585) 
  • 国技館 (旧字旧仮名、作品ID:52461) 
  • 心の王者 (新字新仮名、作品ID:18346) 
  • 乞食学生 (新字新仮名、作品ID:285) 
  • 五所川原 (新字新仮名、作品ID:45688) 
  • pythonもシンプルです。

    from bs4 import BeautifulSoup
    import urllib.request as req
    
    url = "https://www.aozora.gr.jp/index_pages/person35.html"
    res = req.urlopen(url)
    soup = BeautifulSoup(res, "html.parser")
    
    li_list = soup.select("ol > li")
    for li in li_list:
    	a = li.a
    	if a != None:
    		name = a.string
    		href = a.attrs["href"]
    		print(name, ">", href)
    

    USD/JPYを取得する

    もはや、何通りもあるので、しつこいかもしれんが、、

    from bs4 import BeautifulSoup
    import urllib.request as req
    
    url = "https://stocks.finance.yahoo.co.jp/stocks/detail/?code=usdjpy"
    res = req.urlopen(url)
    
    soup = BeautifulSoup(res, "html.parser")
    price = soup.select_one(".stoksPrice").string
    print("usd/jpy=", price)
    

    [vagrant@localhost python]$ python3 app.py
    usd/jpy= 111.110000

    こんな小国の通貨だれが買うんだ、ということで

    from bs4 import BeautifulSoup
    import urllib.request as req
    
    url = "https://info.finance.yahoo.co.jp/fx/list/"
    res = req.urlopen(url)
    
    soup = BeautifulSoup(res, "html.parser")
    usdjpy = soup.select_one("#USDJPY_chart_bid").string
    eurjpy = soup.select_one("#EURJPY_chart_bid").string
    audjpy = soup.select_one("#AUDJPY_chart_bid").string
    gbpjpy = soup.select_one("#GBPJPY_chart_bid").string
    nzdjpy = soup.select_one("#NZDJPY_chart_bid").string
    cadjpy = soup.select_one("#CADJPY_chart_bid").string
    chfjpy = soup.select_one("#CHFJPY_chart_bid").string
    print("usd/jpy=", usdjpy)
    print("eur/jpy=", eurjpy)
    print("aud/jpy=", audjpy)
    print("gbp/jpy=", gbpjpy)
    print("nzd/jpy=", nzdjpy)
    print("cad/jpy=", cadjpy)
    print("chf/jpy=", chfjpy)
    

    全く問題なく行けますね。
    [vagrant@localhost python]$ python3 app.py
    usd/jpy= 111.099
    eur/jpy= 129.781
    aud/jpy= 82.113
    gbp/jpy= 145.734
    nzd/jpy= 75.621
    cad/jpy= 85.127
    chf/jpy= 111.916

    ただ、表示するだけだと意味がないので、裁定取引にしないと意味ないですね。
    例えば、
    -> ある時間帯に一分毎にスクレイピングして、価格差のボラティリティが一定以上の場合は、自動でロング、ショートポジションを入れる 
    こんなんで勝てるか?