【Team JINIAC】官公庁データセットの収集(Webスクレイピングによる事後学習用データセット作成)
この記事でわかること
この記事では、日本語LLM開発のために、各省庁の大臣記者会見の質疑応答をスクレイピングしてSFT用データセットを作成する方法について解説します。具体的には、以下の内容がわかります。
- 各省庁の記者会見の質疑応答データのスクレイピング方法
- スクレイピングしたデータをJSONL形式で保存する方法
- データセットに’instruction’列を追加して整形する方法
- 実際のスクレイピングコードの例
はじめに
私たち(Team JINIAC)では、日本語LLM開発のためのSFT用データセットを作成するにあたり、高品質なQAデータセットとして各省庁の大臣記者会見の質疑応答に着目し、それらをWebスクレイピングにより収集してSFTデータセットとして整形しましたので、その工程をご紹介します。
作業のアウトライン
今回作成したコードは、各省庁記者会見のうち質疑応答部分だけをスクレイピングし、データセットとして整形するところまでを実行するものです。具体的には、以下のような処理を行っています。
- 各省庁の記者会見の質疑応答が記録されているウェブページからデータを取得(スクレイピング)します。
- 取得したデータを、‘URL’, ‘質問’, '回答’の形式のJSONLファイルとして保存します。
- さらに、各質問と回答に対応する省庁名を示す新しい列’instruction’を追加します。この’instruction’列の値は、「●●省の記者会見における質問と回答」という形式の文字列です。
- 最後に、更新されたデータを同じJSONLファイルに上書き保存します。
このデータセットは日本語LLMの事後学習(SFT)向けに作成するものですので、「質問」「回答」だけでなく、「instruction」を追加しています。また、データソースを明示するため、「URL」も併せて入れておくようにしました。
また、各省庁のwebサイトを調べたところ、省庁ごとにHTML構造が異なるため、それぞれ異なるスクレイピングコードを整備しました。今回対象としたのは、総務省、国土交通省、厚生労働省、文部科学省、農林水産省、金融庁の6省庁です。
データセットの形式
前述のとおり、スクレイピングしたデータは’URL’, ‘instruction’, ‘質問’, '回答’の形式のJsonlファイルとして出力しました。
スクレイピングコードの例
一例ですが、国土交通省サイトからのスクレイピングコードは次のとおりです。
!pip install requests beautifulsoup4
import requests
from bs4 import BeautifulSoup
import time
from urllib.parse import urljoin
import json
import re
# スクレイピング対象のURL
url = "https://www.mlit.go.jp/report/interview/daijin.html"
# URLからHTMLを取得
response = requests.get(url)
response.encoding = response.apparent_encoding # 文字化け対策
# BeautifulSoupオブジェクトを作成
soup = BeautifulSoup(response.text, 'html.parser')
# divタグのclass="menuList01"の中のaタグを取得
a_tags = soup.select('div.menuList01 ul li div p a')
# 各aタグのhref属性の値(リンク先)を取得
links = [a.get('href').replace('\r', '') for a in a_tags] # '\r'を削除
# リンク先を出力
for link in links:
print("'" + link + "',")
def scrape_links(url):
"""
指定されたURLからリンクを抽出します。
Args:
url (str): 処理対象のURL
Returns:
list: 抽出されたリンクのリスト
"""
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
div = soup.find('div', {'class': 'pressList01'})
links = []
if div is not None:
links = [urljoin(url, a.get('href')) for a in div.find_all('a') if a.get('href')]
return links
def scrape_qa(url):
"""
指定されたURLからQAデータを抽出します。
Args:
url (str): 処理対象のURL
Returns:
list: 抽出されたQAデータのリスト
"""
response = requests.get(url)
response.encoding = response.apparent_encoding # 文字化け対策
soup = BeautifulSoup(response.text, 'html.parser')
divs = soup.find_all('div', {'class': 'section'})
qa_list = []
for div in divs:
if div is not None:
text = div.text.strip()
if '(問)' in text:
split_text = re.split('(問)', text)[1:] # Skip the first split as it is before the first question
for i in range(len(split_text)):
question_answer_split = re.split('(答)', split_text[i], 1)
question = question_answer_split[0].strip()
answer = question_answer_split[1].strip() if len(question_answer_split) > 1 else ""
qa_list.append({'URL': url, '質問': question, '回答': answer})
time.sleep(1)
# Remove duplicates
qa_list = [dict(t) for t in set(tuple(d.items()) for d in qa_list)]
return qa_list
# URLを指定して関数を呼び出す
urls = [
'https://www.mlit.go.jp/report/interview/R3daijin.html',
'https://www.mlit.go.jp/report/interview/R2daijin.html',
'https://www.mlit.go.jp/report/interview/H31daijin.html',
'https://www.mlit.go.jp/report/interview/H30daijin.html',
'https://www.mlit.go.jp/report/interview/H29daijin.html',
'https://www.mlit.go.jp/report/interview/H28daijin.html',
'https://www.mlit.go.jp/report/interview/H27daijin.html',
'https://www.mlit.go.jp/report/interview/H26daijin.html',
'https://www.mlit.go.jp/report/interview/H25daijin.html',
'https://www.mlit.go.jp/report/interview/H24daijin.html',
'https://www.mlit.go.jp/report/interview/H23daijin.html',
'https://www.mlit.go.jp/report/interview/H22daijin.html',
'https://www.mlit.go.jp/report/interview/H21daijin.html',
'https://www.mlit.go.jp/report/interview/kanekodaijin08.html',
'http://www.mlit.go.jp/kaiken/kaiken08/history.html'
]
# JSON形式で出力
with open('QA_milt.jsonl', 'a') as f:
for url in urls:
links = scrape_links(url)
for link in links:
qa_list = scrape_qa(link)
#print(qa_list)
for item in qa_list:
f.write(json.dumps(item, ensure_ascii=False) + '\n')
上記コードの内容は次のとおりです。
ライブラリのインポート
import requests
from bs4 import BeautifulSoup
import time
from urllib.parse import urljoin
import json
import re
ここでは、必要なPythonモジュールをインポートしています。requests
はHTTPリクエストを送るためのモジュール、BeautifulSoup
はHTMLやXMLの解析を行うためのモジュール、time
は時間に関する操作を行うためのモジュール、urljoin
はURLを結合するための関数、json
はJSON形式のデータを扱うためのモジュール、re
は正規表現を扱うためのモジュールです。
スクレイピング対象のURLからHTMLを取得
# スクレイピング対象のURL
url = "https://www.mlit.go.jp/report/interview/daijin.html"
# URLからHTMLを取得
response = requests.get(url)
response.encoding = response.apparent_encoding # 文字化け対策
# BeautifulSoupオブジェクトを作成
soup = BeautifulSoup(response.text, 'html.parser')
この部分では、スクレイピング対象のURLからHTMLを取得し、そのHTMLを解析するためのBeautifulSoup
オブジェクトを作成しています。response.apparent_encoding
を使うことで、文字化けを防ぐことができます。
リンクの取得
# divタグのclass="menuList01"の中のaタグを取得
a_tags = soup.select('div.menuList01 ul li div p a')
# 各aタグのhref属性の値(リンク先)を取得
links = [a.get('href').replace('\r', '') for a in a_tags] # '\r'を削除
# リンク先を出力
for link in links:
print("'" + link + "',")
この部分では、取得したHTMLから特定のaタグ(リンク)を取得し、そのリンク先(href属性の値)を取得しています。具体的には、div
タグのclass
属性が"menuList01"
である要素の中のa
タグを取得しています。そして、各a
タグのhref
属性の値から改行文字(\r
)を削除し、その結果をリンクのリストとして保存しています。
スクレイピング関数の定義
def scrape_links(url):
...
def scrape_qa(url):
...
この部分では、リンクの抽出とQAデータの抽出を行う2つの関数を定義しています。scrape_links
関数は指定されたURLからリンクを抽出し、scrape_qa
関数は指定されたURLからQAデータを抽出します。
スクレイピングの実行と結果の保存
# URLを指定して関数を呼び出す
urls = [
'https://www.mlit.go.jp/report/interview/R3daijin.html',
'https://www.mlit.go.jp/report/interview/R2daijin.html',
'https://www.mlit.go.jp/report/interview/H31daijin.html',
'https://www.mlit.go.jp/report/interview/H30daijin.html',
'https://www.mlit.go.jp/report/interview/H29daijin.html',
'https://www.mlit.go.jp/report/interview/H28daijin.html',
'https://www.mlit.go.jp/report/interview/H27daijin.html',
'https://www.mlit.go.jp/report/interview/H26daijin.html',
'https://www.mlit.go.jp/report/interview/H25daijin.html',
'https://www.mlit.go.jp/report/interview/H24daijin.html',
'https://www.mlit.go.jp/report/interview/H23daijin.html',
'https://www.mlit.go.jp/report/interview/H22daijin.html',
'https://www.mlit.go.jp/report/interview/H21daijin.html',
'https://www.mlit.go.jp/report/interview/kanekodaijin08.html',
'http://www.mlit.go.jp/kaiken/kaiken08/history.html'
]
# JSON形式で出力
with open('QA_milt.jsonl', 'a') as f:
for url in urls:
links = scrape_links(url)
for link in links:
qa_list = scrape_qa(link)
#print(qa_list)
for item in qa_list:
f.write(json.dumps(item, ensure_ascii=False) + '\n')
この部分では、スクレイピングを実行し、その結果をJSON形式でファイルに保存しています。具体的には、指定された各URLに対してscrape_links
関数を呼び出し、取得した各リンクに対してscrape_qa
関数を呼び出しています。そして、取得したQAデータをJSON形式の文字列に変換し、それをファイルに書き込んでいます。
instruction列の追加、整形
日本語LLMの事後学習用データセットの作成を目的としているため、スクレイピングしたデータに’instruction’列を追加しました。
以下に、google Colabのコードを示します。
import json
import pandas as pd
from google.colab import drive
# Google Driveをマウント
drive.mount('/content/drive')
# ファイル名と対応する省庁名の辞書
files = {
'QA_mhlw.jsonl': '厚生労働省',
'QA_maff.jsonl': '農林水産省',
'QA_milt.jsonl': '国土交通省',
'QA_soumu.jsonl': '総務省',
'QA_fsa.jsonl': '金融庁',
'QA_monka.jsonl': '文部科学省'
}
# 各ファイルに対して処理を行う
for filename, ministry in files.items():
path = f'/content/drive/MyDrive/datasets/QA/{filename}' # ファイルのパスを適宜変更してください
with open(path, 'r') as f:
data = [json.loads(line) for line in f]
# 新しい'instruction'列を追加
for item in data:
item['instruction'] = f'{ministry}の記者会見における質問と回答'
# DataFrameとして読み込み、列の順序を調整
df = pd.DataFrame(data)
df = df[['URL', 'instruction', '質問', '回答']]
# ファイルに書き戻す
with open(path, 'w') as f:
for item in df.to_dict('records'):
f.write(json.dumps(item, ensure_ascii=False) + '\n')
まとめ
この記事では、各省庁webサイトから記者会見の質疑応答部分をスクレイピングしてデータセットを作成する過程を紹介しました。作成したデータセットは、Huggingface上に公開する予定です。
💡 この成果は、NEDO(国立研究開発法人新エネルギー・産業技術総合開発機構)の助成事業「ポスト5G情報通信システム基盤強化研究開発事業」(JPNP20017)の結果得られたものです。
東京大学 松尾・岩澤研究室が運営する松尾研LLMコミュニティのLLM開発プロジェクト[GENIAC] の開発記録、情報発信になります。 各種リンクはこちら linktr.ee/matsuolab_community
Discussion