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

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

    beautifulsoupでcssセレクタを使う

    折角なので、apple japanを見てみましょう。
    https://www.apple.com/jp/

    from bs4 import BeautifulSoup
    
    html = """
    <html><body>
    <div class="ac-gf-directory-column"><input class="ac-gf-directory-column-section-state" type="checkbox" id="ac-gf-directory-column-section-state-products" />
    		<div class="ac-gf-directory-column-section">
    			<label class="ac-gf-directory-column-section-label" for="ac-gf-directory-column-section-state-products">
    		<h3 class="ac-gf-directory-column-section-title">製品情報と購入</h3>
    	</label>
    			<a href="#ac-gf-directory-column-section-state-products" class="ac-gf-directory-column-section-anchor ac-gf-directory-column-section-anchor-open">
    				<span class="ac-gf-directory-column-section-anchor-label">メニューを開く</span>
    			</a>
    			<a href="#" class="ac-gf-directory-column-section-anchor ac-gf-directory-column-section-anchor-close">
    				<span class="ac-gf-directory-column-section-anchor-label">メニューを閉じる</span>
    			</a>
    			<ul class="ac-gf-directory-column-section-list">
    				<li class="ac-gf-directory-column-section-item"><a class="ac-gf-directory-column-section-link" href="/jp/mac/">Mac</a></li>
    				<li class="ac-gf-directory-column-section-item"><a class="ac-gf-directory-column-section-link" href="/jp/ipad/">iPad</a></li>
    				<li class="ac-gf-directory-column-section-item"><a class="ac-gf-directory-column-section-link" href="/jp/iphone/">iPhone</a></li>
    				<li class="ac-gf-directory-column-section-item"><a class="ac-gf-directory-column-section-link" href="/jp/watch/">Watch</a></li>
    				<li class="ac-gf-directory-column-section-item"><a class="ac-gf-directory-column-section-link" href="/jp/tv/">TV</a></li>
    				<li class="ac-gf-directory-column-section-item"><a class="ac-gf-directory-column-section-link" href="/jp/music/">Music</a></li>
    				<li class="ac-gf-directory-column-section-item"><a class="ac-gf-directory-column-section-link" href="/jp/itunes/">iTunes</a></li>
    				<li class="ac-gf-directory-column-section-item"><a class="ac-gf-directory-column-section-link" href="/jp/ipod-touch/">iPod touch</a></li>
    				<li class="ac-gf-directory-column-section-item"><a class="ac-gf-directory-column-section-link" href="/jp/shop/goto/buy_accessories">アクセサリ</a></li>
    				<li class="ac-gf-directory-column-section-item"><a class="ac-gf-directory-column-section-link" href="/jp/shop/goto/giftcards">ギフトカード</a></li>
    			</ul>
    		</div>
    	</div>
    </body></html>
    """
    
    soup = BeautifulSoup(html, 'html.parser')
    h3 = soup.select_one("div.ac-gf-directory-column-section > label.ac-gf-directory-column-section-label > h3")
    print("h3 =", h3.string)
    
    li_list = soup.select("div.ac-gf-directory-column-section > ul.ac-gf-directory-column-section-list > li")
    for li in li_list:
    	print("li =", li.string)
    

    [vagrant@localhost python]$ python3 app.py
    h3 = 製品情報と購入
    li = Mac
    li = iPad
    li = iPhone
    li = Watch
    li = TV
    li = Music
    li = iTunes
    li = iPod touch
    li = アクセサリ
    li = ギフトカード

    上手くいけてます。iPodの位置づけがよくわかりませんね。
    h3の箇所は、
    soup.select_one(“div.ac-gf-directory-column-section > label.ac-gf-directory-column-section-label > h3”)
    と書いていますが、
    soup.select_one(“div.ac-gf-directory-column-section > h3”)
    と書くと、エラーになります。

    Traceback (most recent call last):
    File “app.py”, line 35, in
    print(“h3 =”, h3.string)
    AttributeError: ‘NoneType’ object has no attribute ‘string’

    階層に沿って書かないと駄目ということでしょう。

    xmlからparseする

    駅データ.jpさんのxmlからparseします。

    from bs4 import BeautifulSoup
    import urllib.request as req
    
    url = "http://www.ekidata.jp/api/s/1130224.xml"
    res = req.urlopen(url)
    soup = BeautifulSoup(res, 'html.parser')
    
    code = soup.find("line_cd").string
    line = soup.find("line_name").string
    station = soup.find("station_name").string
    print(code, line, station)
    

    [vagrant@localhost python]$ python3 app.py
    11302 JR山手線 東京

    しかし、これ、どうやって駅のデータ収集しているんだろうか。
    http://www.ekidata.jp/agreement.php

    駅探など、乗り換えのサービスは素晴らしいよね。

    bs4 find_all()

    aタグを全て取得したいとする。

    from bs4 import BeautifulSoup
    
    html = """
    <html><body>
    	<table id="tp_market_sub" style="margin:4px 0 0 20px; font-size:13px;">
    		<tr>
    		<td><a href="/news/marketnews/?b=n201807300012">シカゴ日経・ADR</a> <img src="/images/cmn/new.gif" title="new"/></td>
    		<td><a href="/news/marketnews/?b=n201807300013">米国株式概況</a> <img src="/images/cmn/new.gif" title="new"/></td>
    		<td><a href="/news/marketnews/?b=n201807300014">今日の注目スケジュール</a> <img src="/images/cmn/new.gif" title="new"/></td>
    		</tr>
    	</table>
    </body></html>
    """
    
    soup = BeautifulSoup(html, 'html.parser')
    
    links = soup.find_all("a")
    
    for a in links:
    	href = a.attrs["href"]
    	text = a.string
    	print(text + " > " + href)
    

    [vagrant@localhost python]$ python3 app.py
    シカゴ日経・ADR > /news/marketnews/?b=n201807300012
    米国株式概況 > /news/marketnews/?b=n201807300013
    今日の注目スケジュール > /news/marketnews/?b=n201807300014

    find_allで複数条件を指定したいよね。

    html = """
    <html><body>
    	<table id="tp_market_sub" style="margin:4px 0 0 20px; font-size:13px;">
    		<tr>
    		<td id="tp_market_1"><a href="/news/marketnews/?b=n201807300012">シカゴ日経・ADR</a> <img src="/images/cmn/new.gif" title="new"/></td>
    		<td id="tp_market_1"><a href="/news/marketnews/?b=n201807300013">米国株式概況</a> <img src="/images/cmn/new.gif" title="new"/></td>
    		<td id="tp_market_2"><a href="/news/marketnews/?b=n201807300014">今日の注目スケジュール</a> <img src="/images/cmn/new.gif" title="new"/></td>
    		</tr>
    	</table>
    </body></html>
    """
    
    links = soup.find_all(id="tp_market_1", "a")
    

    これだと上手くいかない。なんでだろう。。
    soup.find_all(‘タグ名’, ‘属性’) だからか。。

    BeautifulSoup(html, ‘html.parser’)

    from bs4 import BeautifulSoup
    
    html = """
    <html><body>
    	<h1>決算速報 【リアルタイム配信中】</h1>
    	<p>07/27 17:30 菱友システム、4-6月期(1Q)経常は7.8倍増益で着</p>
    	<p>07/27 16:45 D・アクシス、上期経常を一転8%減益に下方修正</p>
    </body></html>
    """
    
    soup = BeautifulSoup(html, 'html.parser')
    
    h1 = soup.html.body.h1
    p1 = soup.html.body.p
    p2 = p1.next_sibling.next_sibling
    
    print("h1 = " + h1.string)
    print("p = " + p1.string)
    print("p = " + p2.string)
    

    ここは普通ですね。
    [vagrant@localhost python]$ python3 app.py
    h1 = 決算速報 【リアルタイム配信中】
    p = 07/27 17:30 菱友システム、4-6月期(1Q)経常は7.8倍増益で着
    p = 07/27 16:45 D・アクシス、上期経常を一転8%減益に下方修正

    h1が1つ、pタグが2つなんてサイトはありませんから、ここからが本番です。

    html = """
    <html><body>
    	<h1 id="news">決算速報 【リアルタイム配信中】</h1>
    	<p id="d1">07/27 17:30 菱友システム、4-6月期(1Q)経常は7.8倍増益で着</p>
    	<p id="d2">07/27 16:45 D・アクシス、上期経常を一転8%減益に下方修正</p>
    </body></html>
    """
    
    soup = BeautifulSoup(html, 'html.parser')
    
    h1 = soup.find(id="news")
    p1 = soup.find(id="d1")
    p2 = soup.find(id="d2")
    
    print("h1 = " + h1.string)
    print("p = " + p1.string)
    print("p = " + p2.string)
    

    [vagrant@localhost python]$ python3 app.py
    h1 = 決算速報 【リアルタイム配信中】
    p = 07/27 17:30 菱友システム、4-6月期(1Q)経常は7.8倍増益で着
    p = 07/27 16:45 D・アクシス、上期経常を一転8%減益に下方修正

    h1 = soup.find(class="new")
    p1 = soup.find(class="d3")
    p2 = soup.find(class="d4")
    

    classにすると、invalid errorになる。何故だ?
    [vagrant@localhost python]$ python3 app.py
    File “app.py”, line 13
    h1 = soup.find(class=”new”)
    ^
    SyntaxError: invalid syntax