Help us understand the problem. What is going on with this article?

JobPosting構造化データをクローリングする

この記事は ただの集団 AdventCalendar PtW.2019 の1日目の記事です。
明日は kzone さんの DjangoをTerraformでFargateにデプロイする話 です。

はじめに

今までは求人サイトから求人情報をクロールして集めようとしても、各サイトで作りがバラバラなので自動で正確なデータを収集することが困難だった。
しかし、 "Google for jobs" がJobPosting形式のクローリングを始めたことで、各求人サイトが求人情報をJobPosting形式で掲載するようになってきたため、求人情報クローリングが今までより簡単になるかもしれない。

JobPosting構造化データとは?

  • JobPosting構造化データ とは、検索エンジン用に様々な構造化データを定義している schema.org が作った求人用の構造化データ定義。
  • 現在は34項目程度のプロパティがある。
  • Microdata, RDFa, JSON-LDの3形式に対応している。

JSON-LDの場合の例

<script type="application/ld+json">
{
  "@context": "http://schema.org/",
  "@type": "JobPosting",
  "name": "Mobile App Developer",
  "hiringOrganization": {
    "@type": "Organization",
    "name": "ACME Software",
  },
  "relevantOccupation": {
    "@type": "Occupation",
    "name": "Software Developers, Applications",
    "occupationalCategory": "15-1132.00"
  }
}
</script>

JobPostingに対応している求人サイト

「求人」でGoogle検索して上位にきたサイトのうち、ログインしないと求人情報が見れないサイトを除いた上位30サイトを調査したところ21サイトが対応していて、7割のサイトが既に対応済みということになる。
特筆すべきは対応していた全サイトがGoogle推奨のJSON-LD形式であること。

siteName url JobPosting
エン転職 https://employment.en-japan.com/
リクルートエージェント https://www.r-agent.com/
Indeed https://jp.indeed.com/
タウンワーク https://townwork.net/
求人ボックス https://求人ボックス.com/
はたらいく https://www.hatalike.jp/
とらばーゆ https://toranet.jp/
フロムエー https://www.froma.com/
ハローワークの求人を検索 https://www.hellowork.careers/
マイナビバイト https://baito.mynavi.jp/
しゅふJOBパート https://part.shufu-job.jp/
バイトル https://www.baitoru.com/
キャリアインデックス https://careerindex.jp/
an https://weban.jp/
ディースターNET https://d-starjob.com/
ハローワークインターネットサービス https://www.hellowork.go.jp/index.html
マイナビミドルシニア https://mynavi-ms.jp/
イーアイデム https://www.e-aidem.com/
doda https://doda.jp/
求人ジャーナルネット https://www.job-j.net/
女の転職type https://woman-type.jp/
Green https://www.green-japan.com/
dジョブ https://job.dmkt-sp.jp/
ものコレ https://monocolle.jp/work/
クックビズ https://cookbiz.jp/
YAHOO!しごと検索 https://feature-job.yahoo.co.jp/
求人@飲食店.COM https://job.inshokuten.com
求人情報もってけ! https://mtke-job.jp/
しゅふきた https://www.shufukita.jp/
転職会議 https://jobtalk.jp/

GoogleのJobPostingの扱い

必須プロパティ

プロパティ名 内容 所感
datePosted 雇用主が求人情報を投稿した最初の日付(ISO 8601 形式)。 前回と変更があるかの確認や新着表示のために使えそう
description HTML形式での求人の詳細な説明。 HTML形式なので色々な表現ができそう
hiringOrganization 職位を提供している組織。 ロゴも含められるようなので視認性も豊かになりそう
jobLocation オフィスや作業現場など、従業員の職場となる特定の場所(求人情報を投稿した場所ではない)。 かなり柔軟に細かく入力できるみたい
title 職務の名称(求人情報のタイトルではない)。 目立つ文言を入れたくなるけど職務の名称のみが推奨されている
validThrough 求人情報が期限切れになる日付(ISO 8601 形式)。 必須と言いつつ有効期限がある場合のみで良いみたい

推奨プロパティ

プロパティ名 内容 所感
applicantLocationRequirements 従業員がリモートワークを行うために所在する必要のある地域。 リモートワークに関する項目が推奨されているのがGoogleらしい
baseSalary 雇用主から提示された実際の基本給(概算額ではない)。 時給〜年収、min〜maxなど細かく指定できる
employmentType 雇用形態。 雇用形態を知らずに応募できないので必須でも良い気がする
identifier 求人に関する採用側組織の一意の識別子。 掲載側でIDが付けられるのは嬉しい
jobLocationType 業務時間中、自宅など本人が選択した場所で常にリモートワークする求人の場合、このフィールドに TELECOMMUTE を設定します。 リモートワークに関するプロパティが多い

JobPosting構造化データを取得する

Scalaとjsoupを使って実際にJobPosting構造化データを取得してみる。
ソースは job-postiong-crawl-sample にアップした。

import org.jsoup.Jsoup
import io.circe._, io.circe.parser._

object Main extends App {
  val detailUrl = "https://cookbiz.jp/job/job54024.html"
  val jobPostingScriptTag = Jsoup.connect(detailUrl).get().select("script[type=\"application/ld+json\"]")
  val jobPostingJson = parse(jobPostingScriptTag.html()) match { case Right(json) => json }

  // required values
  println("datePosted=[%s]".format(extract(jobPostingJson, "datePosted")))
  println("description=[%s]".format(extract(jobPostingJson, "description")))
  println("hiringOrganization=[%s]".format(extract(jobPostingJson, "hiringOrganization", "name")))
  println("jobLocation=[%s]".format(extract(jobPostingJson, "jobLocation", "addressRegion")))
  println("title=[%s]".format(extract(jobPostingJson, "title")))
  println("validThrough=[%s]".format(extract(jobPostingJson, "validThrough")))

  // recommended values
  println("applicantLocationRequirements=[%s]".format(extract(jobPostingJson, "applicantLocationRequirements")))
  println("baseSalary=[%s: %s~%s]".format(extract(jobPostingJson, "baseSalary", "unitText"), extract(jobPostingJson, "baseSalary", "minValue"), extract(jobPostingJson, "baseSalary", "maxValue")))
  println("employmentType=[%s]".format(extract(jobPostingJson, "employmentType")))
  println("identifier=[%s.%s]".format(extract(jobPostingJson, "identifier", "name"), extract(jobPostingJson, "identifier", "value")))
  println("jobLocationType=[%s]".format(extract(jobPostingJson, "jobLocationType")))

  def extract(json: Json, keys: String*) = {
    val result = keys.foldLeft(json)((j, k) => j.\\(k) match {
      case head :: _ => head
      case _ => Json.Null
    })
    if (result == Json.Null) "" else result.toString
  }
}

実行結果

datePosted=["2019-04-26"]
description=["◆大人から子どもまで、みんなが満たされる大衆寿司居酒屋「回転寿司だと子どもたちは楽しめるけど、お父さんはちょっと物足りない…」「カウンターの寿司店だと、子どもを連れていくには懐も雰囲気も気がきでない」そんなファミリー層もお腹と舌を満たせるような寿司店をめや盛り付け、寿司の提供を習得してください。対面式カウンターなので、調理をしながら接客や会話もお願いします。また、ホールでの接客も経験してください。店長就任後は、シフト管理・棚卸・発注・計数管理など店舗運営全般をお任せします。◆たくさんの「楽しい!」が生まれる場所としすることで、お客さまがスタッフに話しかけてくれます。そこからコミュニケーションが生まれ、お店の雰囲気もにぎやかになるんです。提供した料理を見た瞬間「なにこれ!」と驚いたり喜んだり。とにかくその場にいるみんなが「楽しい!」と思えるお店づくりを心掛けています。お客さまに喜んでもらえる仕組みをたくさん作っていきませんか?"]
hiringOrganization=["株式会社スシロークリエイティブダイニング"]
jobLocation=["東京23区"]
title=["店舗スタッフ(店長・副店長候補)"]
validThrough=[]
applicantLocationRequirements=[]
baseSalary=["MONTH": 306000~370800]
employmentType=["FULL_TIME"]
identifier=["株式会社スシロークリエイティブダイニング"."54024"]
jobLocationType=[]

値の入っている項目は全て取得できた。
かなり汎用的な作りが可能になりそう。

まとめ

今まで正確に自動化することが難しかった求人情報クローリングの分野に"Google for Jobs"が参入したことによって、JobPosting構造化データでの求人公開が一般的になりつつあり、今後は自動化がかなり楽になりそう。

参考にした本・サイト

クローリングハック あらゆるWebサイトをクロールするための実践テクニック
Webスクレイピングの注意事項一覧
JobPosting
Googleの求人情報に関する構造化データ

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away