search
LoginSignup
1

More than 3 years have passed since last update.

posted at

pathlibやBeautifulSoupでurlの一覧からMarkdown用のリンクを生成するバッチを作った

pathlibやBeautifulSoupでurlの一覧からMarkdown用のリンクを生成するバッチを作った

目的

  • ブログやQiita用にMarkdown記法を使っているけど、リンクにタイトル付けるのが面倒くさい
  • urlだけでタイトル勝手に取ってくれないかな
    • JavaScriptのブックマークレットなら簡単だろうけど、単体でしか使えないからなあ
    • やりようによっては複数もいけそうですが、その点は有識者にお任せします
  • urlだけ1行ずつ書いたファイルを読み込んでMarkdownを吐き出すようにしたい
  • 確認とコピペが楽なようにExcel向けのcsvファイル(ShiftJISエンコード)で出力しよう
    • Excelファイルでも良かったんですが、他の環境とかOpenOfficeとかでも対応出来るようにcsvで
    • まあ、そのせいで以前の記事でまんまと引っかかったエンコード問題にまた苦戦したんですが1

使ったライブラリ

ライブラリ名 だいたいの内容
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']を指定して削除
  • ignore_str
    • ページタイトルに、ファイルに出力できない文字があった場合は無視して、とりあえず出力できる分だけ出力するために記述
    • encode時に'ignore'(エラーになる文字は無視)指定で何とかなるそう3

read_url(urlからtitleを取得し書き出す)

正規表現の処理

regex
    # 正規表現処理
    rep = r'^https?://[\w/:%#\$&\?\(\)~\.=\+\-]+$'
    isUrl = re.match(rep, url) != None

ページの取得

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ブロックをまた外のメソッドに書き出そうかと考えています

出力結果(兼、参考にした記事等)

url title Markdown link
https://qiita.com/tag1216/items/29bf3b9c3c4b563ff0fe Python3.4以降ならos.pathはさっさと捨ててpathlibを使うべき - Qiita Python3.4以降ならos.pathはさっさと捨ててpathlibを使うべき - Qiita
https://docs.python.jp/3/library/pathlib.html 11.1. pathlib § Python 3.6.4 \ [11.1. pathlib § Python 3.6.4 ](https://docs.python.jp/3/library/pathlib.html)
https://qiita.com/tossh/items/635aea9a529b9deb3038 忘れっぽい人のための正規表現チートシート - Qiita 忘れっぽい人のための正規表現チートシート - Qiita
http://www.megasoft.co.jp/mifes/seiki/s310.html £¨5 £¨
https://qiita.com/meznat/items/a1cc61edb1e340d0b1a2 Pathlibチートシート - Qiita Pathlibチートシート - Qiita
https://qiita.com/Azunyan1111/items/9b3d16428d2bcc7c9406 Python Webスクレイピング 実践入門 - Qiita Python Webスクレイピング 実践入門 - Qiita
https://qiita.com/urahito_solution/items/c37a11ce48291d34d589 PythonでCドライブ内を圧迫しているファイル・フォルダを一覧化する - Qiita PythonでCドライブ内を圧迫しているファイル・フォルダを一覧化する - Qiita
https://qiita.com/butada/items/33db39ced989c2ebf644 (Windows) Python3でのUnicodeEncodeErrorの原因と回避方法 - Qiita (Windows) Python3でのUnicodeEncodeErrorの原因と回避方法 - Qiita
https://qiita.com/tag1216/items/fd5379ab813111a5e364 dictの作成は「{}」より「dict()」のほうが簡潔に書ける - Qiita dictの作成は「{}」より「dict()」のほうが簡潔に書ける - Qiita
https://qiita.com/sea_ship/items/7c8811b5cf37d700adc4 正規表現の基本 - Qiita 正規表現の基本 - Qiita

参考書籍(再登場)

例のごとくソース全体はこちら

今後やりたいこと

  • csvからMarkdownの表レイアウト勝手にしてくれるやつ作りたい(エディタでチマチマ置換するのが面倒くさい)

  1. 今回もお世話になりました: Windows Python3でのUnicodeEncodeErrorの原因と回避方法 - Qiita 

  2. 参考: dictの作成は「{}」より「dict()」のほうが簡潔に書ける - Qiita 

  3. Qiitaのどこかでこの書き方を見た気がするのですが、思い出せないので後日思い出せたら追記します 

  4. Python公式ドキュメント(日本語版)の11.1.1.2節参照 

  5. 正規表現のurlパターンのサンプルです。"£¨"ってミスってるのは、タイトル取得時に何やら問題があったらしいです(そのうち調べて補足記事立てます) 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1