pathlibやBeautifulSoupでurlの一覧からMarkdown用のリンクを生成するバッチを作った
目的
- ブログやQiita用にMarkdown記法を使っているけど、リンクにタイトル付けるのが面倒くさい
- urlだけでタイトル勝手に取ってくれないかな
- JavaScriptのブックマークレットなら簡単だろうけど、単体でしか使えないからなあ
- やりようによっては複数もいけそうですが、その点は有識者にお任せします
- urlだけ1行ずつ書いたファイルを読み込んでMarkdownを吐き出すようにしたい
- 確認とコピペが楽なようにExcel向けのcsvファイル(ShiftJISエンコード)で出力しよう
使ったライブラリ
ライブラリ名 | だいたいの内容 |
---|---|
csv | csvファイルの書き出しを楽にする |
bs4(BeautifulSoup) | html(xml)のパース(内部的に扱えるデータにすること)に利用 |
requests | urlを渡してhtmlファイルをgetしてくる |
re | 正規表現の処理に使う |
pathlib | パス周りの処理を扱う。osやos.pathの拡張のようなもの。openメソッドなども持っている |
解説だけ(ソースは最後の方にリンクを置きました)
ワンライナーなメソッドまわり
method
# 指定エンコードのgetメソッド
def get_enc(mode):
enc_dic = dict(r='utf-8', w='sjis', p='cp932')
return enc_dic[mode]
# インラインのfor文リストで除外文字以外を繋ぐ
def remove_str(target, str_list):
return ''.join([c for c in target if c not in str_list])
# 指定エンコードでエラー文字以外を再取得する
def ignore_str(target, enc):
return target.encode(enc, 'ignore').decode(enc)
- get_enc
- いちいちエンコード指定で入れるのが面倒だったのでdict(辞書型)で定義2
- remove_str
- target文字列(url)内の文字を
for~in
で取り出し、if~not in~
で除外リストにない文字を''.join(~)
でつなげる表現 -
text_file.readlines()
で読み込んだ行テキストには改行文字が入るそうなので、除外リストに['\r', '\n']
を指定して削除
- target文字列(url)内の文字を
- ignore_str
- ページタイトルに、ファイルに出力できない文字があった場合は無視して、とりあえず出力できる分だけ出力するために記述
- encode時に'ignore'(エラーになる文字は無視)指定で何とかなるそう3
read_url(urlからtitleを取得し書き出す)
正規表現の処理
regex
# 正規表現処理
rep = r'^https?://[\w/:%#\$&\?\(\)~\.=\+\-]+$'
isUrl = re.match(rep, url) != None
- urlのパターン文字列は色々参考にしました
ページの取得
get_url
# ページの取得
res = requests.get(url)
soup = bs4.BeautifulSoup(res.text)
- 1行目でhtmlファイルを取得して
- 2行目で内部的にテキストデータとして扱えるようにする(パースする)
タイトルの取得から出力まで
get_title_and_output
# タイトルの取得とMarkdown用フォーマット
title = ignore_str(soup.title.string, get_enc('w'))
markup = '[{}]({})'.format(title, url)
# csvファイルへ書き出し
out_writer.writerow([url, title, markup])
-
soup.title.string
でタイトルの中身だけ取ってこれるので利用- 取得したタイトルは先に定義したignore_strとget_encで処理
- 変数markupへのフォーマットは、
'[{}]({})'.format(title, url)
だけで済む- つまり、title→urlの順に{}で割り当てた位置に代入される
- 以前の記事より簡単にurl→title→markupの順で書き出し
mainメソッド
pathlibまわり
main-1
# 各ディレクトリの取得
parent_dir = Path(__file__).parent
org_dir = parent_dir / 'org'
out_dir = parent_dir / 'out'
# フォルダが無ければ作成(あってもエラーなし)
org_dir.mkdir(exist_ok=True)
out_dir.mkdir(exist_ok=True)
# 出力ファイルの決定
out_file = out_dir / 'result.csv'
-
__file__
で.pyファイルのパスを取得し、Path(path).parentで親ディレクトリの取得 - pathlibは結構便利で、
/
演算子がオーバーライド(機能の上書き)されて、os.path
などPythonで扱う/
記号で繋げる感覚で使えます4 -
pathlib.mkdir(exist_ok=True)
のexist_ok
は、mkdirしたときに既に存在していた場合、エラーとせずに(True)そのままに出来る指定
読み込みと出力
main-2
# 入力、出力ファイル・ディレクトリの取得
files = list(org_dir.glob('*.txt'))
with out_file.open('w', encoding=get_enc('w'), newline='') as out_file_obj:
# csvファイルのwriterを取得
out_writer = csv.writer(out_file_obj, dialect="excel")
# 入力ファイルでfor文を回す
for file_path in files:
with Path(file_path).open('r', encoding=get_enc('r')) as read_file_obj:
# 全行取り込む
url_list = read_file_obj.readlines()
# urlの読み込み
read_url(url_list, out_writer)
- 頭の
org_dir.glob('*.txt')
はちゃんとlist()
でリスト化しないとfor文で使えないので注意 - withブロック(
class.method() as instance
)は、C#でいうusing
ブロックに相当します- つまり、内部的に
try-finally
ブロックが自動で構成され、finallyブロック内でinstance.close()
が呼ばれるので、エラーが起きた際にも安心
- つまり、内部的に
- あとはコメントの通りですが、forブロックをまた外のメソッドに書き出そうかと考えています
- The Zen of Python(参考記事)にも「ネストは浅い方が良い」とありますしね
出力結果(兼、参考にした記事等)
参考書籍(再登場)
例のごとくソース全体はこちら
今後やりたいこと
- csvからMarkdownの表レイアウト勝手にしてくれるやつ作りたい(エディタでチマチマ置換するのが面倒くさい)
-
今回もお世話になりました: Windows Python3でのUnicodeEncodeErrorの原因と回避方法 - Qiita ↩
-
Qiitaのどこかでこの書き方を見た気がするのですが、思い出せないので後日思い出せたら追記します ↩
-
正規表現のurlパターンのサンプルです。"£¨"ってミスってるのは、タイトル取得時に何やら問題があったらしいです(そのうち調べて補足記事立てます) ↩