Webスクレイピング:記事のURLを抽出する

本連載ではディープラーニングを用いた自然言語処理の手順について紹介していきます。筆者自身もまた,ディープラーニングではじめて Python に触れた初心者です。他の言語は多少扱ったことはあるが,Python はほとんど分からないという人を対象にした記事となります。

ディープラーニングは難しいと思われるかもしれませんが,コードを書いて実行するだけなら簡単です。ここではディープラーニングの仕組みを理解するよりも,それを実際に利用して結果を得ることを目標とします。

実行環境

一般的に,windows では Anaconda を使って Python を実行します。その他にも様々なパッケージをインストールする必要がありますが,それらのインストール手順については省略します。また,ディープラーニングは膨大な計算を行うので,GPUが必須となります。しかし,そうしたPCを持っていなくても,Google Colaboratory を使えば無料でGPUを利用できるので,はじめはそちらから体験すると良いでしょう。

ディープラーニングの手順

ディープラーニングには,1.前処理,2.学習,3.評価,という3つの手順があります。

ディープラーニングを用いて何か実用的なものを作るには,良質なデータの準備が大切です。
機械に効率よく学習させるために,データを加工する必要があります。この作業を前処理と呼び,ある意味ディープラーニングで最も手間のかかる部分と言えます。

今回作るもの

事例として,今回は英語の文章を自動生成する AI を作ります。学習用のデータとしてはアメリカの VOA Learning English のサイトに掲載されている記事を使用します。

著作権について

ウェブサイトに掲載されている情報を用いると著作権の懸念が出てきます。しかしながら,幸いなことに日本の法律ではウェブサイトの情報を自分のハードディスクにコピーすることについては問題ありません。個人的な利用に留めておく分にはあまり心配する必要はありません。詳しくはこちらの記事などを参考にしてください。

スクレイピング

ウェブサイトから必要なデータを抽出することをスクレイピングを言います。スクレイピングの手順はそれぞれのウェブサイトによって異なります。今回は VOA Learning English から記事を抽出します。

トップページから As it is のカテゴリーページに移動します。

いくつかの記事の見出しが並んでいます。ここから,それぞれの記事の URL を取得します。

import numpy as np
import requests
from bs4 import BeautifulSoup
import io
import re
url = 'https://learningenglish.voanews.com/z/3521'
res = requests.get(url)
soup = BeautifulSoup(res.text, 'html.parser')
elems = soup.find_all(href=re.compile("/a/"))
links = []
for i in range(len(elems)):
    links.append('https://learningenglish.voanews.com'+elems[i].attrs['href'])
links = np.unique(links)
text='\n'.join(links)
with io.open('article-url.txt', 'w', encoding='utf-8') as f:
    f.write(text)

中身を見ていきましょう。

import numpy as np
import requests
from bs4 import BeautifulSoup
import io
import re

これらは拡張機能のインポートです。Python は多くの拡張機能を利用します。

numpy は計算を効率よく行うための機能です。as np と書くことで,以下の行で np.~ と省略した形として登場してきます。

requests はホームページの html を抜き出すために使います。以下の行で requests.~ となっているところでこの機能を使っています。

BeutifulSoup は html を解析して必要な部分だけを抜き出すために使います。

この 3 つの拡張機能は事前にインストールが必要です(インストール手順は省略)。

io はファイルの入出力で使います。また,re は正規表現を扱うためのものです。

url = 'https://learningenglish.voanews.com/z/3521'
res = requests.get(url)

拡張機能 requests を使っています。

html を抽出するアドレスを文字列 url に格納し,requests.get(url) で抽出します。情報を格納する res は文字列ではなくオブジェクトです。オブジェクトは様々な情報がつまった箱としてイメージするとよいでしょう。上のように書くことで,res の中に html の文字列を含め,いくつかの情報がまとめて放り込まれます。

soup = BeautifulSoup(res.text, 'html.parser')

拡張機能 BeautifulSoup を使っています。

res.text は,res というオブジェクトの中の text というラベルを指しています。html の文字列を書き記した紙に text と書いた付箋を貼って,res という名前の箱の中に放り込んでいる状態をイメージするとよいでしょう。res.text には 以下のような html 文字列が格納されています。

<!DOCTYPE html>
<html lang="en" dir="ltr" class="no-js">
<head>
<link href="/Content/responsive/VOA/en-US-LEARN/VOA-en-US-LEARN.css?&av=0.1.0.0&cb=144" rel="stylesheet"/>
<script src="//tags.tiqcdn.com/utag/bbg/voa-pangea/prod/utag.sync.js"></script> <script type='text/javascript' src='https://www.youtube.com/iframe_api'></script>
<script type="text/javascript">

・・・・・

これを BeutifulSoup に与えて,soup というオブジェクトに情報を格納します。こうして,html を解析するための専用の箱に情報を移し替えます。

elems = soup.find_all(href=re.compile("/a/"))

soup.find_all() でオブジェクト soup から記事へのリンクの部分を抽出します。

html の中では,それぞれの記事へのリンクは,<a href='記事のURL'> ~ </a> という形で記述されています。

href=re.compile("/a/") と記述すると,html の中から<a href='記事のURL'> ~ </a>の部分だけを取り出し,さらにその中から /a/ という文字を含むものだけを取り出して,elems というリスト(配列)に格納します。これは,VOA Learning English のサイトではそれぞれの記事の URL が https://learningenglish.voanews.com/a/~ という形になっているからです。

この状態では,elems にはタグを含んだ状態の文字列が格納されているので,ここからさらに URL だけを取り出します。

links = []
for i in range(len(elems)):
    links.append('https://learningenglish.voanews.com'+elems[i].attrs['href'])

links = [] は,links がリストであることを示しています。

for ~文は繰り返し処理を行います。他のプログラミング言語の for 文に比べて,書き方がやや独特かもしれません。他の言語では for 文による繰り返しの範囲を { } で指定することが多いのですが,Python は字下げによって指定します。字下げを間違えるとループの範囲も変わってくるので,注意が必要です。

range() は繰り返しの範囲を指定します。例えば for i in range(10): とすると,i0 から 9 まで変化します。ここでは,len(elems) として,elems の要素数だけ繰り返しています。たとえば,抽出したアドレスが 5 個あるとしたら,elemselems[0] ~ elems[4] まで存在しています。このとき,len(elems) = 5 です。

elems[i].attrs['href'] はタグを含んだ文字列 elems から href の中身だけを抜き出します。例えば

elems[0] = '<a href="/a/new-us-citizens-look-forward-to-voting/5538093.html">'

ならば

elems[0].attrs['href'] = '/a/new-us-citizens-look-forward-to-voting/5538093.html'

です。

URL にはドメイン名が含まれていないので,'https://learningenglish.voanews.com' を付け加えます。

links.append() は,リスト(配列)である links に要素を追加する機能です。ここでは,links に抽出した URL を 1 つずつ格納しています。

しかし,コードを実行して links の中身を確認してみると

https://learningenglish.voanews.com/a/5526946.html
https://learningenglish.voanews.com/a/5526946.html
https://learningenglish.voanews.com/a/new-us-citizens-look-forward-to-voting/5538093.html
https://learningenglish.voanews.com/a/new-us-citizens-look-forward-to-voting/5538093.html
https://learningenglish.voanews.com/a/coronavirus-stops-starts-testing-europeans-patience/5543763.html
https://learningenglish.voanews.com/a/coronavirus-stops-starts-testing-europeans-patience/5543763.html

・・・・・・

URL が重複して抽出されました。

links = np.unique(links)

ここで最初に述べた,数値計算ライブラリ numpy を使います。インポートするときに,numpynp という文字列で表すと宣言したので,numpy ではなく np と書きます。

unique() は重複している要素を削除します。このように numpy を使うとデータの整理を簡単に行うことができます。ここでは,重複したデータを削除し,再び links に格納します。

text='\n'.join(links)

links に格納された文字列を .join() で連結します。'\n'.join() と書くと,連結する文字列の間に改行コードを挿入することができます。こうして,1行に1つずつ URL が書かれた文字列 text が作られます。

with io.open('article-url.txt', 'w', encoding='utf-8') as f:
    f.write(text)

最後に文字列textarticle-url.txt というファイル名で書き込みます。

プログラムを実行します。たとえば,ファイル名が sample.py なら,プロンプト画面で

python sample.py

と入力します。実行後,新しく作られたテキストファイルを開くと

https://learningenglish.voanews.com/a/5543923.html
https://learningenglish.voanews.com/a/after-multiple-crises-this-time-lebanese-feel-broken-/5542477.html
https://learningenglish.voanews.com/a/coronavirus-stops-starts-testing-europeans-patience/5543763.html

となり,記事の URL が抽出されたことが確認できます。