概要
別の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を学びたいと思っている方は是非こちらもどうぞ↓