6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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パターンのサンプルです。"£¨"ってミスってるのは、タイトル取得時に何やら問題があったらしいです(そのうち調べて補足記事立てます)

6
2
0

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
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?