ハローワークの求人情報をスクレイピング(Python + Selenium + BeautifulSoup)

この記事は、以下のハローワークインターネットサービスから求人情報を自動で取得する試みを記録したものです: www.hellowork.mhlw.go.jp

まずは、ソースコードと実行結果をお見せし、後ほどこの記事を書いた経緯などを話します。

ソースコード:HelloWork_Scraping_ST.py

from selenium import webdriver
from selenium.webdriver.support.ui import Select
import time
from bs4 import BeautifulSoup
import re

# ハローワークインターネットサービスのURL
url = "https://www.hellowork.mhlw.go.jp/"

# 以下からご自分で使用しているChromeのバージョンに合ったChromeDriverをダウンロードして下さい
# https://chromedriver.chromium.org/downloads

# ChromeDriverをご自分のPCの任意の場所に保存して、以下のDRIVER_PATHに設定して下さい。
DRIVER_PATH = 'D:\source\py\ChromeWebDriver\chromedriver_win32\chromedriver.exe'
driver = webdriver.Chrome(executable_path=DRIVER_PATH)
driver.get(url)
time.sleep(1)

# 「求人情報検索」をクリック
driver.find_element_by_class_name("retrieval_icn").click()
time.sleep(1)

# 福岡県内で探す
element = driver.find_element_by_id("ID_tDFK1CmbBox")
Select(element).select_by_value("40") #福岡
time.sleep(1)

# 「市区町村」を選ぶために「選択」をクリック
buttons = driver.find_elements_by_css_selector("input.button");
buttons[1].click()
time.sleep(1)

# 市区町村名のドロップダウンリストを選択
element = driver.find_element_by_id("ID_rank1CodeMulti")

# 【東日本】市区町村名コード(5桁・6桁)一覧(更新:2019.9.15)
# http://www13.plala.or.jp/bigdata/municipal_code_1.html

# 【西日本】市区町村名コード(5桁・6桁)一覧(更新:2019.9.15)
# http://www13.plala.or.jp/bigdata/municipal_code_2.html

# 市区町村名コードを基に、5つまで市区町村名を選択
Select(element).select_by_value("40131") #東区
Select(element).select_by_value("40132") #博多区
Select(element).select_by_value("40133") #中央区
Select(element).select_by_value("40134") #南区
Select(element).select_by_value("40135") #西区
time.sleep(1)

# OKをクリック
driver.find_element_by_id("ID_ok").click()
time.sleep(1)

# 佐賀県内で探す
element = driver.find_element_by_id("ID_tDFK2CmbBox")
Select(element).select_by_value("41") #佐賀
time.sleep(1)

# 「市区町村」を選ぶために「選択」をクリック
buttons = driver.find_elements_by_css_selector("input.button");
buttons[2].click()
time.sleep(1)

# 市区町村名のドロップダウンリストを選択
element = driver.find_element_by_id("ID_rank1CodeMulti")

# 市区町村名コードを基に、5つまで市区町村名を選択
Select(element).select_by_value("41201") #佐賀市
Select(element).select_by_value("41203") #鳥栖市
time.sleep(1)

# OKをクリック
driver.find_element_by_id("ID_ok").click()
time.sleep(1)

# 長崎県内で探す
element = driver.find_element_by_id("ID_tDFK3CmbBox")
Select(element).select_by_value("42") #長崎
time.sleep(1)

# 「市区町村」を選ぶために「選択」をクリック
buttons = driver.find_elements_by_css_selector("input.button");
buttons[3].click()
time.sleep(1)

# 市区町村名のドロップダウンリストを選択
element = driver.find_element_by_id("ID_rank1CodeMulti")

# 市区町村名コードを基に、5つまで市区町村名を選択
Select(element).select_by_value("42202") #佐世保市
Select(element).select_by_value("42204") #諫早市
Select(element).select_by_value("42205") #大村市
time.sleep(1)

# 「OK」をクリック
driver.find_element_by_id("ID_ok").click()
time.sleep(1)

# 「職業分類を選択」をクリック
buttons = driver.find_elements_by_css_selector("input.button");
buttons[7].click()
time.sleep(1)

# 「職業分類」のドロップダウンリストを選択
element = driver.find_element_by_id("ID_rank00Code")

# 「B 専門的・技術的職業」を選択
Select(element).select_by_value("B")

# 「下位」をクリック
driver.find_element_by_id("ID_down").click()
time.sleep(1)

# 「下位」のドロップダウンリストを選択
element = driver.find_element_by_id("ID_rank00Code")

# 「10 情報処理・通信技術者」を選択
Select(element).select_by_value("10")

# 「下位」をクリック
driver.find_element_by_id("ID_down").click()
time.sleep(1)

# 「下位」のドロップダウンリストを選択
element = driver.find_element_by_id("ID_rank00Code")

# 「104 ソフトウェア開発技術者」を選択
Select(element).select_by_value("104")

# 「下位」をクリック
driver.find_element_by_id("ID_down").click()
time.sleep(1)

# 「10401 ソフトウェア開発技術者(WEB・オープン系)」が既に選択されているので、「決定」をクリック
driver.find_element_by_id("ID_ok").click()
time.sleep(1)

# 「フリーワード」の「OR検索」ラジオボタンをクリック
driver.find_element_by_id("ID_freeWordRadioBtn0").click()
time.sleep(1)

# 「フリーワード」の入力欄をクリック
element = driver.find_element_by_id("ID_freeWordInput")

# 「フリーワード」の入力欄に"Python C#"と入力(注意:全角文字で入力すること!)
element.send_keys("Python C#")

time.sleep(1)

# 「詳細検索条件」をクリック
driver.find_element_by_id("ID_searchShosaiBtn").click()
time.sleep(1)

# 「交代制(シフト制)を含まない」にチェック
driver.find_element_by_id("ID_LkiboShgJnCKBox1").click()
time.sleep(1)

# 「裁量労働制を含まない」にチェック

driver.find_element_by_id("ID_LkiboShgJnCKBox2").click()
time.sleep(1)
# 「変形労働時間制を含まない」にチェック
driver.find_element_by_id("ID_LkiboShgJnCKBox3").click()
time.sleep(1)

# 「派遣・請負を含まない」にチェック
driver.find_element_by_id("ID_hakenUkeoinCKBox3").click()
time.sleep(1)

# 「転勤の可能性なし」にチェック
driver.find_element_by_id("ID_LsonotaCKBox4").click()
time.sleep(1)
# print(driver.page_source)

# 「OK」をクリック
driver.find_element_by_id("ID_saveCondBtn").click()
time.sleep(1)

# 「検索」をクリック
driver.find_element_by_id("ID_searchBtn").click()
time.sleep(1)

# 「表示件数」ドロップダウンリストをクリック
element = driver.find_element_by_id("ID_fwListNaviDispBtm")

# 「50件」を選択
Select(element).select_by_value("50")
time.sleep(1)

# 今見ているページをBeautifulSoupで解析
soup = BeautifulSoup(driver.page_source, "html.parser")

# 「求人」のテーブルを検索
jobs = soup.find_all("table", attrs={"class": "kyujin"})

# 検索結果を格納する"message"を初期化
message = ""

# 「職種」「月給」「勤務地」「仕事の内容」を取得する
for i, job in enumerate(jobs):
    job_name = str(job.find("td", attrs={"class": "m13"}).text.strip())
    salary_tags = job.find_all("tr",attrs={"class": "border_new"})[5].select(".disp_inline_block")
    for t, salary_tag  in enumerate(salary_tags):
        job_salary = salary_tag.text

    # 月給の上限の金額を正規表現で抽出
    m = re.search('(〜)(\d{3}),(\d{3})', job_salary)
    highest = int(m.group(2) + m.group(3))
    
    # 月給の上限の金額が40万円以上だった場合にのみ、"message"に格納する
    if highest >= 400000:
        print("〇 ", highest)
        job_description = job.find(string='仕事の内容').parent.find_next_sibling().text.replace('\n', '')
        job_location = job.find(string='就業場所').parent.find_next_sibling().text.replace('\n', '')
        message = message + "■{0} ( {1} ){2} \n□{3}\n".format(job_name, job_salary, job_location, job_description)
    else:
         print("× ", highest)

# 検索結果の出力
print(message)

# 「次へ」をクリックして、次ページを表示する
# TODO: 「次へ」がクリックできる限り、以下の表示をループで回す、多分簡単
# TODO: CSV形式で保存してもいい
driver.find_element_by_name("fwListNaviBtnNext").click()
time.sleep(5)

# 今見ているページをBeautifulSoupで解析
soup = BeautifulSoup(driver.page_source, "html.parser")

# 「求人」のテーブルを検索
jobs = soup.find_all("table", attrs={"class": "kyujin"})

# 検索結果を格納する"message"を初期化
message = ""

# 「職種」「月給」「勤務地」「仕事の内容」を取得する
for i, job in enumerate(jobs):
    job_name = str(job.find("td", attrs={"class": "m13"}).text.strip())
    salary_tags = job.find_all("tr",attrs={"class": "border_new"})[5].select(".disp_inline_block")
    for t, salary_tag  in enumerate(salary_tags):
        job_salary = salary_tag.text

    # 月給の上限の金額を正規表現で抽出
    m = re.search('(〜)(\d{3}),(\d{3})', job_salary)
    highest = int(m.group(2) + m.group(3))
    
    # 月給の上限の金額が40万円以上だった場合にのみ、"message"に格納する
    if highest >= 400000:
        print("〇 ", highest)
        job_description = job.find(string='仕事の内容').parent.find_next_sibling().text.replace('\n', '')
        job_location = job.find(string='就業場所').parent.find_next_sibling().text.replace('\n', '')
        message = message + "■{0} ( {1} ){2} \n□{3}\n".format(job_name, job_salary, job_location, job_description)
    else:
         print("× ", highest)

# 検索結果の出力
print(message)
# driver.close()

実行結果(一応、伏字で伏せています):

■************在宅勤務を導入中◆(○○市) ( 250,000円〜500,000円 )○○県○○市○○区 
□************WEB言語を使用。設計済みで開発をメイン工程。
■************在宅勤務を導入中◆(○○市) ( 250,000円〜500,000円 )○○県○○市○○区 
□************WEBへの新規展開に向けたリプレースPJです。
■************務可) ( 202,000円〜580,000円 )○○県○○市○○区 
□************ネット関連ソフト開発
■************在宅勤務可) ( 202,000円〜580,000円 )○○県○○市○○区 
□************上心のあるエンジニアの求人です。
■************ ( 220,000円〜400,000円 )○○県○○市○○区 
□************(言語:C,C**、C#、JAVA,VB、PHP等)
■************(正)/○○区 ( 250,000円〜460,000円 )○○県○○市○○区 
□************製造、テスト
                    :

ハローワークインターネットサービスで仕事を検索すると、不便なことがあります。
まず、1ページに50件までしか表示できないので、検索結果が比較しづらいですね。
あと、以前は給与で検索できたのですが、今は廃止されたようです。
まぁ、元々、200,000円~500,000円みたいな給与の幅で提示されているんで、
給与で検索してもあまり意味が無い、という理由もあったと思います。
それでも、やっぱり一定金額以上の給与のみを表示できると便利ですよね。

ということで、PythonSeleniumとBeautifulSoupを使って
ハローワークから自動的に検索結果を取得(スクレイピング)することにしました。

ただ、1から自分で作ったのではなく、
以下の記事を基に書きました
(作者の齊藤さん、この場をお借りしてありがとうございます!):

www.geek.sc

そして、自分のニーズに合わせて、幾つかの項目を追加しました。
その際には、以下の回答を参考にしました:

teratail.com

例えば、複数の勤務地を選択してみました。
ご自分で勤務地を変更する場合は、勤務地の市町村コードを以下から見つけて下さい:

www13.plala.or.jp www13.plala.or.jp

あと、PythonC#をキーワードとしてOR検索したり、
月給の上限が40万円以上の案件しか表示しないようにしてあります。

私が付け加えた部分については、後日コードの説明を追記する予定です。
また、齊藤さんの記事に元々ある部分については、
私は説明を控えますので、齊藤さんの記事をご覧ください。

注意書きですが、検索で見つかった求人情報は
ハローワークインターネットサービスに掲載されているサイトポリシーに沿ってご利用下さい: www.hellowork.mhlw.go.jp

また、ハローワークインターネットサービスのレイアウトが変更されれば、
私のコードも動作しなくなるのでご注意ください。
(レイアウトが変更されたからといって、
 ハローワークインターネットサービスに問い合わせないで下さいね。)

もし、私の記事を基にご自分のニーズに合った改造が出来たなら、
履歴書に「Pythonスクレイピングが出来ます」と書いてもよいのではないでしょうか?

皆様のお仕事探しの助けになれば光栄です。

・・・以上です。