アンカータグに入ったJavaScriptスキームのサイトにScrapyで挑む
Scrapyしにくいサイト
ここです。
jinzai.hellowork.mhlw.go.jp
ちょっとアクセスすると判るのですが、href
にJavaScript:
に入れまくっている(JavaScriptスキーム)平成初期の臭いが漂うサイトですね。
今回は、Scrapyを使ってスクレイピングしてみます。
Scrapyの下準備
Scrapyの説明も含めて、こちらをごらんください。
qiita.com
非常に解りやすいので、オススメです。
デベロッパーツールはともだち
Scrapyは何でもいけますが、リクエスト内容がわからないと画面遷移ができないので調査します。
サイトにアクセスしてから、デベロッパーツールを開きます。
※POSTMANなどツールもありますが、今回は使わないでいきます。
ご存知だと思いますが [表示] - [開発 / 管理] - [デベロッパーツール]でデベロッパーツールで開きます。(ショートカットキーでもOK)
そしてNetworkに切り替えます。
start_urlsのURLを探索する
調査したいリンクを押下します。
そして、Networkで表示されたリソースの一番上を押下して情報を所得したら、一番下までスクロールします。
Form Dataが必要な情報になります。
Form Dataにトークンらしきものが無ければ、このForm Dataのデータを使ってGet Methodにしてアクセスしてみます。
Get Methodにできればコードが減るので、このURLをとりあえず覚えておきます。
※Post Methodのみだったら、ここからコードを書くことになります。
さらにもっとできないか探索します。
調査を続けます、次に都道府県の東京都をチェックしてから検索を押下します。
同じようにForm Dataを見て、トークンらしきものが無ければForm Dataのデータを使ってGet Methodにしてアクセスをします。
そうすると、アクセスできたので、このURLもとりあえず覚えておきます。
トークンがある
検索結果を全て取得したいので、次ページのページネーションを確認します。
画面の下の2
を押下します。
そうすると、ついにトークンらしきものが現れました。
ここからは、Post Methodの方が良さそうなので、さきほど覚えたURLをScrapyのstart_urls
としてコードを書きます。
コードを書く
会社名を取得するので、items.py はcompany_name
と書く。
# -*- coding: utf-8 -*- # Define here the models for your scraped items # # See documentation in: # https://docs.scrapy.org/en/latest/topics/items.html import scrapy class Post(scrapy.Item): company_name = scrapy.Field()
spiders/hellowork.py はこんな感じです。
# -*- coding: utf-8 -*- import re import scrapy from .. items import Post class HelloworkSpider(scrapy.Spider): name = 'Hellowork' allowed_domains = ['jinzai.hellowork.mhlw.go.jp'] start_urls = ['https://jinzai.hellowork.mhlw.go.jp/JinzaiWeb/GICB102010.do?action=search&screenId=GICB102010&cbTokyo=1'] def parse(self, response): for post in response.css('table#search tr'): name = post.css('span#ID_lbJigyoshoName::text').extract_first() if name is None: continue yield Post( company_name = name ) # 現在のページ数を取得する for current in response.css('table tr td[style="width: 60%;"]'): current_page = current.css('span#ID_lbSearchCurrentPage::text').extract_first() if current_page is None: return # 次のページがあるか確認する next_page = int(current_page) + 1 has_next_page = False for id_pager_tag in response.css('table tr td[style="padding:5px;"]'): id_pager_href = id_pager_tag.css('a::attr(href)').extract_first() if id_pager_href is not None: find_array = re.findall('[0-9]+', id_pager_href) if len(find_array) == 0: continue if int(find_array[0]) == next_page: has_next_page = True next_page = str(next_page) break # 次ページが無ければ終了する if has_next_page is False: return # 次のページをリクエストして、解析用parseに渡す yield scrapy.FormRequest.from_response( response, formdata = dict( screenId = 'GICB102010', action = 'page', cbTokyo = '1', curPage = current_page, params = next_page), callback = self.parse, dont_click = True ) return
説明
parseの
response.css('table#search tr'):
から会社名を取得します。
会社名はID名がsearchのtableタグのの中にあるのでresponse.css('table#search tr')
というので検索せます。
会社名自体はspanタグのID名がID_lbJigyoshoNameのテキスト項目なのでspan#ID_lbJigyoshoName::text
で引っ張り出します。
なお、tableタグの中にはtrには何も定義が無いので、余分なtrも検索してしまうので、spanタグがうまく取れなかったらスルーします。次ページ
みたいのがないので、現在ページを取得+1を計算してページネーションと比較する方法を取ります。
現在ページ + 1 in ページネーション数値
を次ページとしたいので、まずは現在ページを取得します。
会社一覧の上に小さく現在ページが表示されているので、そこのタグspan#ID_lbSearchCurrentPage::text
がページ数らしいですので、それを取得します。次はページネーションのところを探索します。
table tr td[style="padding:5px;"]
を配列で取得して、a::attr(href)
のJavaScriptスキームを取得して、そこから正規表現で数値だけを取り出します。
なんで、こんな面倒なことをしているのかというと、上記のページネーションは世間一般のページネーションとは異なっているからで、次ページ表記っぽい>>
が実は「10*次ページ数+1」という謎ルールで、それなら素直にアンカーダグのJavaScriptスキームを読んだほうが早いからです。現在ページ + 1 in ページネーション数値
と判定できれば次ページが存在する、無ければ存在しないのでプログラムは終了とする。次ページに遷移させるところは、トークンらしきものがあるので、ここはFormRequestを使います。
formdata
に色々とデータを押し込んで、callback
はparse
を再帰呼び出しで次ページが無くなるまで繰り返すようにして、dont_click
をTrueにしてJavaScriptスキームを発火させています。
動作結果
scrapy crawl Hellowork
で実行すると、以下のように会社名が取れていることが確認できます。
なお、CSVファイルに出力したいときはscrapy crawl Hellowork -o company_name.csv
とします。
今回、このサイトをスクレイピングして思ったのは、JavaScriptスキームも面倒でしたが、それ以上に面倒と思ったのは、タグにID名が振っていないと、スクレイピングしにくいわ!と心底思いました。
そのため、自分でサイトを作るときは、なるべくタグにはID名を振ろうと反省するのでありました。