PythonのライブラリであるBeautifulSoup4を用いて、webサイトからスクレピングをするメモ。
今回対象となるwebサイトは、企業価値検索サービス「Ullet」です。
Ulletとは
決算情報や従業員数などの、上場企業のデータが掲載されているwebサイトです。
近年の小洒落たwebサイトと違い、懐かしさのある無骨なデザイン。ドメイン+企業IDで遷移できるため、Seleniumを使わなくてもいいかなとなりました。
robots.txtは下記の通り。
User-agent: * Sitemap: http://www.ullet.com/sitemap.xml User-agent: Hatena Antenna Disallow: /
「Hatena Antenna」以外は明示的なDenyは無し。
環境
BeautifulSoup4はpip install
します。
pip install beautifulsoup4
作成したクローラ
フロー
- ランキングページから各企業ページのURLを生成
- BeautifulSoup4を用いてURLからHTMLを取得
- HTMLを解析して必要なデータを抽出
ページ遷移は昨日別の記事で言及したように、遷移後のURLを叩いても意味がないため、別の方法を用います。
ただし、今回はSeleniumは使いません。
遷移ボタンクリック時にhttp://www.ullet.com/search/page/{対象のページ番号}.html
へリクエストが投げられていることを確認したため、このURLを動的に生成します。
メイン
def scrapy_ullet(): rows = [] # 総ページ数の取得 res = requests.get(URL_ULLET_SEARCH) soup = BeautifulSoup(res.text, 'html.parser') li_all = soup.find('ul', class_='mg_menu_tab mg_menu_tab_top_reverse').find_all('li') max_page_txt = li_all[len(li_all)-1].span.a.get_text() max_page_num = int(re.search('\d+', max_page_txt).group()) for i in range(1, max_page_num+1): read_page(i, rows) write_csv(rows)
総ページ数分read_page()
を呼び出し、最後に取得結果をcsvに出力します。
ページ読み込み
def read_page(page, rows): print(datetime.now().strftime('%Y/%m/%d %H:%M:%S'), 'page%d'%page) res = requests.get(URL_ULLET_PAGE%page) soup = BeautifulSoup(res.text, 'html.parser') a_all = soup.find('div', id='ranking').find_all('a', class_='company_name') for a in a_all: rows.append(get_company_detail(URL_ULLET_TOP+a.get('href')))
対象ページの企業情報を取得してrows
に格納します。
企業情報取得
def get_company_detail(url): res = requests.get(url) soup = BeautifulSoup(res.text, 'html.parser') # サーバー負荷軽減のため一定時間待機する time.sleep(REQUEST_WAIT_TIME) row = [] row.append(re.search('\d+', url).group()) #コード row.append(soup.find('a', id='company_name0').get_text()) #企業名 row.append(trim_indicator(soup, 0)) #売上高 row.append(trim_indicator(soup, 1)) #純利益 row.append(trim_indicator(soup, 2)) #営業CF row.append(trim_indicator(soup, 3)) #総資産 row.append(soup.find('table', class_='company_outline').tbody.find_all('tr')[2].td.get_text()) #従業員数(単独) row.append(soup.find('table', class_='company_outline').tbody.find_all('tr')[3].td.get_text()) #従業員数(連結) row.append(soup.find('table', class_='company_outline').tbody.find_all('tr')[4].td.get_text()) #平均年齢(単独) row.append(soup.find('table', class_='company_outline').tbody.find_all('tr')[5].td.get_text()) #平均勤続年数(単独) row.append(soup.find('table', class_='company_outline').tbody.find_all('tr')[7].td.get_text()) #業種 return row
企業詳細ページの至るところから、正規表現を用いて気合でデータを取得します。
BeautifulSoup.find()
は最初に適合した要素のみを返却するため、条件に気をつけること。
また、短時間で大量のリクエストを投げないよう、RUQUEST_WAIT_TIME
で指定した時間待機しています。今回は1秒を設定してあり、2020年1月3日アクセス時点で3,736社掲載されているため、このスクリプトの実行には1時間以上かかります。これはもう仕方ない。
データ整形
def trim_indicator(soup, index): i = soup.find('tbody').find_all('tr')[1].find_all('td')[index].get_text() r = re.search('.*円', i) if r != None: return r.group() else: return ""
経営指標の4つについてはHTML構成の都合で、get_text()
に余分な値が含まれていたり、そもそも値が存在しなかったりするため、get_company_detail()
から切り出して処理を書いています。
csv出力
def write_csv(rows): with open("ullet.csv", "w", encoding='utf-8') as f: writer = csv.writer(f, delimiter=",", quotechar='"', quoting=csv.QUOTE_ALL) for row in rows: writer.writerow(row)
quotechar='"'
とquoting=csv.QUOTE_ALL
を指定することで、全ての値をダブルクォーテーションで囲っています。
以下コード全文。
あとがき
わりと短くまとまった気がしています。
今回使わなかったSeleniumを取り入れたり、結果をDBに保存したり、lambdaに置いて実行したりすると、大変ですが勉強になります。もう既に忘れかけているため、また今度やり直して記事にまとめられたらいいなあ。
それと、PCを購入したばかりで、ちまちまと環境構築しているのですが、Macに最初からPythonが入ってることに気づいて驚きました。2系なのでさらに驚きました。ちょうど2020年1月1日でPython2系はサポート終了とのこと。
環境構築は面倒ですね。早く整えたいです。