はじめに
Pythonの次のライブラリ/便利メソッドの簡単な紹介です。
- BeautifulSoup4のfind_allであるhtmlの全てのimgタグとそのsrc属性の抜き出し
- urllib.parse.urljoin(こちらの記事https://itdepends.hateblo.jp/entry/2020/02/02/142148)
- os.path.commonpathによる、ディレクトリ名・ファイル名の一覧から、最大共通上位ディレクトリの抜き出し
紹介といいながらも、上記を個人的な昔話にからめながら、スニペット事例をペタペタとはってみたというところになります。
昔話
CMSの移行時などにコンテンツデータを整備したいといったケースではあるあるではないかと思うのですが、現行のコンテンツのHTML(特に一点もののページ)において、共有画像がただしく共有ルールにしたがって共有されているかということをチェックしたいことがあります。
また、逆の話として、本来はあるページでのみ利用するべき個別画像が必要以上に他のページから直リンクされており、掲載期限との兼ね合いの運用をいたずらに難しくしているというものがないかチェックしたいというのもあるでしょう。
この辺の話が難しいのは、大半はマークアップ規約やCMS製品の仕組みでうまく取り計らっているものの、いかんせん「コンテンツ」というのは主観によるところが大きく、せいぜい半構造化の範囲である程度ゆるやかな運用にせざるを得ないため、例外がつきものというところです。
具体的には、下の図のように(図の例は多少デフォルメ・単純化されていますが)、理想どおりになっているOKパターンとそうでない例外となってしまったNGパターンを調査するといったことが考えられます。
以前は、結構この手のチェックにいろいろバッドノウハウ含め手間を要していた用に思いますが、最近はそもそもこういうことが起きにくくなってきたこともありましょうが、BeautifulSoupやらのスクレイピング系のライブラリなどを活用すれば、ワンライナーに毛が生えたぐらいのスクリプトでさくっと調査できてしまうなぁと感じる次第です。
スニペット例
ということで事例です。
(1)あるディレクトリ配下の全てのHTMLを読み込みimgタグのsrc属性を抜き出す
import glob from bs4 import BeautifulSoup as sp import bs4 from urllib.parse import urljoin import sys # ルート相対ディレクトリで走行したとする。配下の拡張子がhtmlのファイルを読み込みする。 # マシンスペックに見合わないようなファイル数だと破綻するので注意。最低限、iglobにするとかは必要かも。 files = [{ 'fn': i, 'file':open(i).read()} for i in glob.glob('**',recursive=True) if i.endswith('.html')] # エラー処理は省略 chklist = [] for f in files: bsobj = sp(f['file'], 'html.parser') fname = '/' + f['fn'] # ルート相対ディレクトリで走行した扱いのため「/」を補う。 imgsrc = [ [fname, urljoin(fname,i.get('src')) ] for i in bsobj.find_all('img') if i.get('src')] chklist.extend(imgsrc) print('page,img') print( '\n'.join([ i[0] + ',' + i[1] for i in sorted(chklist,key=lambda x:x[1]) ] ) )
(2)ファイル名→そのファイル名のimgタグのリンク先の関連テーブル風データを元に、共通ルールにしたがって画像管理されているかチェックする
前述の②-OK/NGパターンを判定しています。
本来は、(1)の続きですが、ここでは、コピペでも動作例が確認できるように、インプットデータはプログラム内のCSV風文字列にしています。
import pandas as pd import io import sys import os csv = """ page,img /xxx/a/b/foo.html,/xxx/a/img_ng/ng.jpg /xxx/a/b/bar.html,/xxx/a/img_ng/ng.jpg /xxx/boo.html,/xxx/a/img_ng/ng.jpg /xxx/a/b/foo.html,/xxx/a/img/i.jpg /xxx/a/b/bar.html,/xxx/a/img/i.jpg """ # 実際はpandasを介すほどではないかもしれない。 # しかし、手軽なCSVの読み込みと、ループ処理を宣言的に実装できることと、今回はそこまでやっていないが他に何かやりたい時のshape変更の伸び代を期待して、pandasを利用。 df = pd.read_csv(io.StringIO(csv)) a = df.groupby('img')['page'].apply(list).reset_index() def 期待通り共有されているか判定(s): IMGDIR_PREFIX = ('/img','/images/') # ここでは「imgで始まるディレクトリ」「imagesディレクトリ」は共通フォルダ用の画像フォルダの命名ルールであるとした。 pages = s['page'] imgpath = s['img'] commonpath = os.path.commonpath(pages) commonpath = os.path.commonpath([imgpath,commonpath]) path = imgpath.replace(commonpath, '') return [pages,imgpath,'共通パス:'+path,'判定' + str(path.startswith(IMGDIR_PREFIX)) ] a.apply(期待通り共有されているか判定,axis=1).to_csv(sys.stdout)
↓実行結果
0,"[['/xxx/a/b/foo.html', '/xxx/a/b/bar.html'], '/xxx/a/img/i.jpg', '共通パス:/img/i.jpg', '判定True']" 1,"[['/xxx/a/b/foo.html', '/xxx/a/b/bar.html', '/xxx/boo.html'], '/xxx/a/img_ng/ng.jpg', '共通パス:/a/img_ng/ng.jpg', '判定False']"
おわりに(ひとりごと)
昔話をおさらいしたくて記事にしてしまった。
標準ライブラリを使って良いのであれば、もっとシンプルかつ堅牢にかけるかもですが、それはデキる人にまかせよう。
あと、記事を書きながら、この類は最近は不要かもと思ったけど、リソースをCDNに置いたりするから逆に必要かもとおも思ったりもした。
参考リンク
os.path.commonpath
commonpathの公式R。
ちなみに、os.pathの上等版のpathlibにはぱっと見、commonpathに対応するものはない模様。
例↓
In [5]: os.path.commonpath(['/xxx/a/b/foo.html', '/xxx/a/b/bar.html', '/xxx/boo.html']) Out[5]: '/xxx'
os.path.commonprefix
...というものもある。名前のとおり。
https://docs.python.org/ja/3/library/os.path.html#os.path.commonprefix
目的外使用ながら、文字列のリストの最初の部分の共通項を抜き出すのにも使える。
しっかり調べたりする時間がが無い場合にはその用途で使っちゃうかも。
In [21]: os.path.commonprefix(['ああ/xxx/a/b/foo.html', 'ああ/xxx/a/b/bar.html', 'ああ/xxx/boo.html']) Out[21]: 'ああ/xxx/'