特定のWebページに掲載されているPDFファイルを一つずつダウンロードするのは手間がかかります。ここでは、Pythonを使用してページ上の全てのPDFファイルを自動でダウンロードするスクリプトを、段階的に解説します。
スクレイピングにおける注意事項
スクリプトを実行する前に、以下の点に留意してください。Webサイトから情報を自動収集する行為(スクレイピング)は、相手のサーバーに負荷をかける可能性があります。
- 利用規約の確認: 対象サイトの利用規約で、スクレイピングが禁止されていないか確認してください。
- サーバーへの負荷: 過度なアクセスは避けてください。処理の合間に
time.sleep()を挟むなど、サーバーに負荷をかけないよう配慮することが推奨されます。 - 著作権: ダウンロードしたコンテンツの取り扱いには注意が必要です。著作権法を遵守し、私的利用の範囲を超えた利用は避けてください。
Step 1: 単一のPDFファイルをダウンロードする
まず、PDFファイルのURLが分かっている場合に、そのファイルをダウンロードする最も基本的なコードを示します。
ここでは、HTTPリクエストを送信するためのライブラリrequestsを使用します。指定したURLに対してGETリクエストを送り、レスポンスとして得られたコンテンツ(バイナリデータ)をファイルに書き込むことでダウンロードを実現します。
import requests # ダウンロードしたいPDFのURL pdf_url = "http://example.com/path/to/your.pdf" # 保存するファイル名 save_name = "downloaded.pdf" # URLにアクセスし、レスポンスを取得 response = requests.get(pdf_url) # レスポンスが正常(ステータスコード200)であればファイルを保存 if response.status_code == 200: # 'wb'はバイナリ書き込みモード with open(save_name, 'wb') as f: f.write(response.content) print(f"'{save_name}' のダウンロードが完了しました。") else: print(f"ダウンロードに失敗しました。ステータスコード: {response.status_code}")
response.contentには、リクエストに対するレスポンスの本体がバイナリ形式で格納されています。画像やPDFなどのバイナリファイルを扱う際は、このようにcontent属性を使用します。
Step 2: Webページから全てのリンクを抽出する
次に、PDFのURLを自動で探すため、指定したWebページ内の全てのリンク(<a>タグ)を抽出します。
HTMLの解析にはBeautifulSoupライブラリが便利です。requestsでページのHTMLコンテンツを取得し、BeautifulSoupで解析可能なオブジェクトに変換後、find_all('a')メソッドで全ての<a>タグをリストとして取得します。
import requests from bs4 import BeautifulSoup # PDFリンクを探したいページのURL target_url = "http://example.com/your-target-page.html" # ページの内容を取得 response = requests.get(target_url) # BeautifulSoupでHTMLを解析 soup = BeautifulSoup(response.content, 'html.parser') # ページ内の全ての<a>タグを検索して表示 links = soup.find_all('a') for link in links: # <a>タグが持つhref属性(リンク先URL)を取得 href = link.get('href') if href: print(href)
補足: BeautifulSoupの第2引数'html.parser'は、Pythonの標準ライブラリを使用してHTMLを解析することを指定しています。
Step 3: PDFへのリンクのみを抽出・整理する
前ステップで取得したリンクには、PDF以外のファイルや別ページへのリンクも含まれています。ここからPDFファイルへのリンクのみを絞り込み、ダウンロード可能な完全なURL形式に整理します。
完成版コード
以下のコードは、これまでのステップを統合し、実用的な形にしたものです。
- 対象ページから
<a>タグを全て抽出します。 - 各
<a>タグのhref属性値が.pdfで終わるものだけを対象にします。 - リンクが相対パス(例:
/files/doc.pdf)の場合、urllib.parse.urljoinを用いて絶対パス(例:http://example.com/files/doc.pdf)に変換します。 - 取得したPDFのURLリストをループ処理し、各ファイルをダウンロードします。ファイル名はURLの末尾から自動で取得します。
- ダウンロードしたファイルを保存するためのディレクトリ(
pdf_downloads)がなければ作成します。
import os import requests from bs4 import BeautifulSoup from urllib.parse import urljoin import time # --- 設定項目 --- # PDFリンクを探したいページのURL target_url = "http://example.com/your-target-page.html" # ファイルを保存するディレクトリ名 save_dir = "pdf_downloads" # ---------------- # 保存用ディレクトリを作成 os.makedirs(save_dir, exist_ok=True) try: # ページの内容を取得 response = requests.get(target_url) response.raise_for_status() # ステータスコードが200番台以外の場合はエラーを発生させる # BeautifulSoupでHTMLを解析 soup = BeautifulSoup(response.content, 'html.parser') # PDFへのリンクを格納するリスト pdf_links = [] # 全ての<a>タグを検索 for link in soup.find_all('a'): href = link.get('href') # href属性があり、かつ末尾が'.pdf'で終わる場合 if href and href.endswith('.pdf'): # 相対パスを絶対パスに変換 full_url = urljoin(target_url, href) pdf_links.append(full_url) if not pdf_links: print("PDFファイルへのリンクが見つかりませんでした。") else: print(f"{len(pdf_links)}個のPDFファイルが見つかりました。ダウンロードを開始します...") # 見つかったPDFリンクを一つずつダウンロード for pdf_url in pdf_links: # URLからファイル名を取得 file_name = os.path.join(save_dir, pdf_url.split('/')[-1]) try: # PDFファイルをダウンロード pdf_response = requests.get(pdf_url) pdf_response.raise_for_status() # ファイルをバイナリ書き込みモードで保存 with open(file_name, 'wb') as f: f.write(pdf_response.content) print(f"'{file_name}' のダウンロードが完了しました。") # サーバーへの負荷を考慮し、1秒待機 time.sleep(1) except requests.exceptions.RequestException as e: print(f"'{pdf_url}' のダウンロードに失敗しました: {e}") except requests.exceptions.RequestException as e: print(f"ページの取得に失敗しました: {e}")
os.makedirs(save_dir, exist_ok=True): 指定したディレクトリを作成します。exist_ok=Trueにより、ディレクトリが既に存在していてもエラーになりません。urljoin(base, url): ベースURL (target_url) とリンクのURL (href) を結合し、完全なURLを生成します。hrefが絶対パスの場合は、そのまま返されます。pdf_url.split('/')[-1]: URLを/で分割し、最後の要素(ファイル名)を取得します。os.path.join(save_dir, ...): 保存ディレクトリ名とファイル名を結合し、OSに適したパス(例:pdf_downloads/document.pdf)を生成します。time.sleep(1): ループの各処理の後に1秒間プログラムを停止させ、短時間に連続してリクエストを送ることを防ぎます。
このスクリプトをベースに、必要に応じてエラーハンドリングやログ出力などの機能を追加することで、より安定したツールとして活用できます。