【コード公開】【初心者のためのPython入門】Webスクレイピング〜サイトを丸ごとダウンロード〜





独学に限界を感じたなら...
とりあえず独学でプログラミング学習を始めたけど、右も左も分からずあなたの時間が無駄に終わるどころか挫折するかもしれません。

あなたが時間を無駄にした分を回収したいなら【Python】2022年最新!おすすめのオンラインプログラミングスクールをご確認ください!

※期間限定で学習ロードマップを記載しています!

概要

別のPythonに関する記事でWebスクレイピングの記事を投稿してきましたが、ここではあるページからリンクされている画像やページを丸ごとダウンロードする方法を紹介します。
サイトからデータを丸ごとダウンロードを実現するために必要な処理が必要になるため、BeautifulSoupやCSSセレクタなどを駆使しますが、それだけでは実現することはできません。
<a>タグのリンク先が相対パスになっている可能性があります。
また、リンク先がHTMLであった時に、そのHTMLの内容をさらに解析する必要があります。
つまり、リンク先を再帰的にダウンロードする必要があります。
ここでは、このような丸ごとダウンロードを実現する方法を紹介していきます。

 

必要なモジュール

今回利用するモジュールは、、、

 

・BeautifulSoup
・urllib(request, urlparse, urljoin, urlretrieve)
・os(makedirs, os.path)
・time
・re

 

上記で挙げたモジュールをインポートして利用していきます。
それでは、ファイルエディタウィンドウを開いて、任意の名前.pyのファイルを作成・保存してください。

 

再帰的にHTMLページを処理すること

ここでは、HTMLページの構造について説明します。

 

 

画像のように、HTMLページはフォルダ構造として考えられます。
「A.html」から「B.html」にリンクしており、「B.html」から「C.html」にリンクしているとします。
ここで、「A.html」からリンクしているページファイルを丸ごとダウンロードしようと考えた時、「C.html」もダウンロードしなければローカルでリンクが切れてしまいます。つまりは「A.html」を解析したあとに「B.html」の内容も解析しなければならないわけです。さらには「C.html」から「D.html」がリンクしていた場合、「D.html」の上位階層である「C.html」も解析しなければならないということです。
HTMLをダウンロードする場合は、再帰的にHTMLを解析していかなければならないわけですね。

 

そして、こうした構造のデータを解析・処理をするためには、関数の再起処理を利用することになります。
再起処理というのは、プログラミング技術の一つのことで、ある関数の中でその関数自身を呼び出すことを言います。
関数”a”がある場合、関数”a”の中で関数”a”を呼び出すことということです。
この技術を利用すれば、HTMLページをダウンロードすることができるわけです。

 

手順としては、以下のサイクルとなります。

 

(1) HTMLを解析
(2) リンクを抽出
(3) 各リンク先にて以下の処理を実行
(4) ファイルをダウンロード
(5) ファイルがHTMLならば、再帰的に(1)からの手順を実行

 

それでは、プログラムを作成していきましょう。

 

ダウンロードするプログラム

from bs4 import BeautifulSoup
import urllib
import urllib.request
from urllib.parse import urlparse
from urllib.parse import urljoin
from urllib.request import urlretrieve
from os import makedirs
import os.path, time, re

 

まずはじめにモジュールの取り込みについてです。
上からHTMLを解析するためのbeautifulSoup、Webに関するさまざまな関数を含んでいるurllib、インターネット上のデータを取得するurllib.request、URLの解決を行うためのurllib.parse、相対パスを展開するためのurllib.parse.urljoin、リモートURLからファイルをダウンロードするためのurllib.request.urlretrieve、ディレクトリを作成するためのos、パスに関連することについて解決するためのos.path、スリープのためのtime、正規表現のためのreをそれぞれインポートします。

 

sugi_files = {}

 

ここでは、グローバル変数として、sugi_filesを初期化します。
これは、HTMLファイルの解析を行ったか判断するための変数となります。
HTMLのリンク構造は、「A.html」から「B.html」にリンクしていた場合、その逆でリンクしている場合もあり得るわけです。
つまり、リンクを何度も解析するたびに、無限ループに陥って処理が終了しないことになります。
そのため、上記のグローバル変数を利用して、同じHTMLは二度と解析しないようにしているわけです。

 

def enum_links(html, base):
	soup = BeautifulSoup(html, "html.parser")
	links = soup.select("link[rel='stylesheet']")
	links += soup.select("a[href]")
	result = []

	for a in links:
		href = a.attrs['href']
		url = urljoin(base, href)
		result.append(url)
	return result

 

この関数では、HTMLを解析してリンクを抽出します。
<a>タグのリンク、<link>タグのCSSの2種類を抽出します。
どちらも、BeautifulSoupのselect()メソッドを利用して抽出します。
下のforループでは、リンクタグのhref属性に記述されているURLを抽出して、urljoinを利用して絶対パスに変換します。

 

def download_file(url):
	o = urlparse(url)
	savepath = "./" + o.netloc + o.path
	if re.search(r"/$", savepath):
		savepath += "index.html"
	savedir = os.path.dirname(savepath)

	if os.path.exists(savepath): return savepath

	if not os.path.exists(savedir):
		print("mkdir=", savedir)
		makedirs(savedir)

	try:
		print("download=", url)
		urlretrieve(url, savepath)
		time.sleep(1)
		return savepath
	except:
		print("ダウンロード失敗:", url)
		return None

 

この関数では、インターネット上からファイルをダウンロードします。
はじめに、元のURLから保存ファイルを指定します。
また、必要に応じてダウンロード先のディレクトリを作成します。
if文のあとは、実際のダウンロードを行うときの処理となります。
一時的に処理を停止するtime.sleep()メソッドを利用して、1秒間待機させています。
これは、ファイルをダウンロードする際にWebサーバに負荷を与えないための処理になります。

 

def analize_html(url, root_url):
	savepath = download_file(url)
	if savepath is None: return
	if savepath in sugi_files: return
	sugi_files[savepath] = True
	print("analize_html=", url)

	html = open(savepath, "r", encoding="utf-8").read()
	links = enum_links(html, url)
	for link_url in links:
		if link_url.find(root_url) != 0:
			if not re.search(r".css$", link_url): continue

		if re.search(r".(html|htm)$", link_url):
			analize_html(link_url, root_url)
			continue

		download_file(link_url)

 

この関数では、HTMLファイルを解析して、リンク先をダウンロードするものとなります。
解析済みなら同じファイルを解析しないようにしてあります。
変数であるhtmlとlinksを利用して、リンクを抽出します。
download_file()関数のurlretrieve()メソッドを利用してダウンロードした後に、
ダウンロード済みのファイルを読み込んで処理するようにしています。
forループでは、リンク先を確認して、リンクが指定サイト外をさしていた時にダウンロードしないように処理しています。
もしもCSSファイルの場合、強制的にダウンロードするよう処理をしています。

 

if __name__ == "__main__":
	url = "任意のURLを入力"
	analize_html(url, url)

 

このif文では、どのサイトをダウンロードするのか指定しています。

 

★site-getall.py★

 

from bs4 import BeautifulSoup
import urllib
import urllib.request
from urllib.parse import urlparse
from urllib.parse import urljoin
from urllib.request import urlretrieve
from os import makedirs
import os.path, time, re

sugi_files = {}

def enum_links(html, base):
	soup = BeautifulSoup(html, "html.parser")
	links = soup.select("link[rel='stylesheet']")
	links += soup.select("a[href]")
	result = []

	for a in links:
		href = a.attrs['href']
		url = urljoin(base, href)
		result.append(url)
	return result

def download_file(url):
	o = urlparse(url)
	savepath = "./" + o.netloc + o.path
	if re.search(r"/$", savepath):
		savepath += "index.html"
	savedir = os.path.dirname(savepath)

	if os.path.exists(savepath): return savepath

	if not os.path.exists(savedir):
		print("mkdir=", savedir)
		makedirs(savedir)

	try:
		print("download=", url)
		urlretrieve(url, savepath)
		time.sleep(1)
		return savepath
	except:
		print("ダウンロード失敗:", url)
		return None

def analize_html(url, root_url):
	savepath = download_file(url)
	if savepath is None: return
	if savepath in sugi_files: return
	sugi_files[savepath] = True
	print("analize_html=", url)

	html = open(savepath, "r", encoding="utf-8").read()
	links = enum_links(html, url)
	for link_url in links:
		if link_url.find(root_url) != 0:
			if not re.search(r".css$", link_url): continue

		if re.search(r".(html|htm)$", link_url):
			analize_html(link_url, root_url)
			continue

		download_file(link_url)

if __name__ == "__main__":
	url = "任意のURLを入力"
	analize_html(url, url)

 

ここまでで今回のプログラムは完了となります。

また、今後もプログラミングに取り組み続けていく中で、実務に利用できる学びを身につけていかなければなりません。

実務に活かす際に学習として利用していたPython本が以下のものになります。

・PythonによるWebスクレイピング(オライリー出版)

・増補改訂Pythonによるスクレイピング&機械学習 開発テクニック(クジラ飛行机)

・Pythonクローリング&スクレイピング – データ収集・解析のための実践開発ガイド

機械学習や分析の分野に興味があり、pythonを学びたいと思っている方は是非こちらもどうぞ↓

誰でも30日間で習得出来るpython学習法

 



ABOUTこの記事をかいた人

sugi

大学卒業後、IT企業に就職を果たす。システム開発・人工知能に触れながら大手企業と業務をこなす。2年半後脱サラし、フリーランス活動経験を経て 2019年2月から起業し、今に至る。 自社サービス及び製品を開発、ブログ収入、クラウドソーシングなど、多方面で売り上げを立てている。