PythonのBeautiful Soupでwebサイトのリンクをたどってコンテンツ一覧を作成

## 概要
webサイトに貼り付けられているCSVファイルやExcel、PDFファイルなどをリストアップするために作成。
基点となるURLを指定し、その中にあるリンクを再帰的に辿ってリンクのリスト、データファイルのリストをそれぞれTSVで出力。

## 使い方
・「base_url」に基点となるURLを設定。サイトマップとか。
・「def_recur_srch_cnt」で再帰探索する回数を指定。
・必要に応じてページ内で検索対象にするタグ、抽出対象とするURLのフィルタ条件を変更

## ソースコード
```py
import sys
import re
import time
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

# url = sys.argv[1]
headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0",}

# リンクを再帰探索する数
def_recur_srch_cnt = 10

# ベースURL
base_url = "https://www.hogehoge.jp/sitemap.html"

# ファイル出力先フォルダ
out_dir = "C:\\work\\tmp\\"
# ファイル名
out_fname_dl = "datalink.txt"
out_fname_pl = "pagelink.txt"
out_fname_tl = "tableaulink.txt"
out_fname_ck = "checked_url.txt"

# 変数初期化
search_urls = []
checked_urls = []
tmp_urls = []
search_urls.append(base_url)

#ファイルオープン
f_dl = open(out_dir + out_fname_dl, "w", encoding='UTF-8')
f_pl = open(out_dir + out_fname_pl, "w", encoding='UTF-8')
f_tl = open(out_dir + out_fname_tl, "w", encoding='UTF-8')

# 初期設定で定義した数だけURL階層を再帰的に検索
for i in range(def_recur_srch_cnt):
	for url in search_urls:
		# 既にチェックしたURLでなければ処理を実行
		if not(url in checked_urls):
			try:
				rr = requests.get(url, headers=headers)
				html = rr.content
					soup = BeautifulSoup(html, "html.parser")
				
					# ページタイトルの取得
					pagetitle = soup.head.title.get_text()
					# リンク先抽出対象となるメインコンテンツ部分の指定
					elements = soup.body.find("div", id="main")
			except Exception as ee:
				sys.stderr.write("*** error *** in requests.get ***\n")
				sys.stderr.write(str(ee) + "\n")
			
			try:
				# ページリンクの抽出
				# .htm又は.htmlのページを対象とするが、"?!"に続くディレクトリを含むものは除外
				for aa in elements.find_all("a", href=re.compile(r'^(?=^(https://www.hogehoge.jp|/|\.+/).*html?[^\.]*$)(?!.*/(en|press)/.*)')):
					# 改行をスペースに置換の上、ドメイン名を省略している場合は付加
					link = re.sub(r'\n', " ", aa.get("href"))
					# さらに相対パスの場合は検索対象ページのURLを絶対パスに変換
					if link.startswith(".") or link.startswith("/"):
						link = urljoin(url, link)
					# リンク名取得
					name = re.sub(r'\n', " ", aa.get_text())
					# ファイル出力
					f_pl.write(str(i) + "\t" + url + "\t" + pagetitle + "\t" + link + "\t" + name + "\n")
					# アンカータグを削除した上で一時URLリストに追加
					tmp_urls.append(re.sub(r'#.+$', "", link))
				
				# データファイルへのリンクの抽出
				for aa in elements.find_all("a", href=re.compile(r'.*\.(csv|CSV|xlsx?|XLSX?|docx?|DOCX?|pptx?|PPTX?|pdf|PDF)$')):
					datalink = re.sub(r'\n', " ", aa.get("href"))
					datatype = re.sub(r'.*\.', "", datalink)
					dataname = re.sub(r'\n', " ", aa.get_text())
					f_dl.write(str(i) + "\t" + url + "\t" + pagetitle + "\t" + datatype + "\t" + datalink + "\t" + dataname + "\n")
			
			except Exception as ee:
				sys.stderr.write("*** error *** in BeautifulSoup ***\n")
				sys.stderr.write(str(ee) + "\n")
			
			# 抽出したリンク情報を重複削除の上で検索対象URLリストに追加）
			tmp_urls.sort()
			search_urls = list(set(tmp_urls))
			# チェック済URLとしてリストに追加
			checked_urls.append(url)
			
			# Sleep 2秒
			time.sleep(2)


#ファイルクローズ
f_dl.close()
f_pl.close()
f_tl.close()

# join()で要素内容を結合
s = "\n".join(checked_urls)
with open(out_dir + out_fname_ck, "w", encoding='UTF-8') as f:
	# 書き込み
	f.write(s)
```

特定のタグを指定する方法もある。
```py
for aa in elements.find_all("div", class_="class_name"):
	id = aa.attrs['id']
	name = aa.noscript.a.img.attrs['alt']
	f_tl.write(str(i) + "\t" + url + "\t" + id + "\t" + name + "\n")
```

## 参考URL

https://qiita.com/ekzemplaro/items/a0dd7dd2bbdcf077a626

https://stackoverflow.com/questions/1080411/retrieve-links-from-web-page-using-python-and-beautifulsoup

https://python-man.club/python-beautifulsoup-how-to-use/