同人音楽即売会M3の出展サークルリストから、サークル名やキーワードを抜こうとしている。何に使うかは未定だけど。
サークルリスト 2018年春 | M3 - 音系・メディアミックス同人即売会 を見てわかるように スペース, サークル名, 概要 となっている。サークルに関する情報はこれだけではなくて、ソースを見てみるとコメントとしてサークル名(カナ)やジャンルコード、キーワードが記述されている。キーワードも取りたくなってしまったのでひたすらググることになったという話。なお、BeautifulSoupに関しての説明はだいぶ省く。
<!-- アナベル A01 女性ヴォーカル ジョセイヴォーカル オリジナル オリジナル ポップス ポップス -->
コメントアウトされた情報はキーワードの数や並びが統一されていないのだけどそれは置いておく。これが実データのつらみか。
コメントタグの中身を取る方法
さて、beautifulsoup comment と検索するとトップにstackoverflowがヒットする。
曰く、find_allメソッドのキーワード引数stringに以下のような無名関数を渡してやれば無名関数を渡してやればコメントが取れる。
lambda text: isinstance(text, Comment)
これで無事コメントタグの中身が取得できてめでたしめでたし、となるけどイマイチ腹落ちしなかったので find_allメソッドを調べた。
BeautifulSoupと無名関数については以下を参考にしたりした。
find_allメソッド
find_allメソッドは引数で指定した条件に一致するタグの要素を返す。
ここではpタグを取得してみよう。
from bs4 import BeautifulSoup as bs markup = """ <body> <h1>First Heading</h1> <p>This is paragraph.</p> <p>spam ham eggs</p> <!-- This is comment --> <!-- foo bar baz --> </body> """ soup = bs(markup, 'html.parser') paras = soup.find_all('p') for p in paras: print(p)
実行結果は以下。
<p>This is paragraph.</p> <p>spam ham eggs</p>
変数pはTagオブジェクトで、get_text()メソッドでタグに囲まれた文字列を取得できる。
This is paragraph. spam ham eggs
となる。
コメントタグの中身を取る方法: 再訪
先の例に当てはめて、コメントタグの中身を取得してみる。
from bs4 import BeautifulSoup as bs from bs4 import Comment markup = """ <body> <h1>First Heading</h1> <p>This is paragraph.</p> <p>spam ham eggs</p> <!-- This is comment --> <!-- foo bar baz --> </body> """ soup = bs(markup, 'html.parser') comments = soup.find_all(string=lambda text: isinstance(text, Comment)) for c in comments: print(c)
This is comment foo bar baz
CommentはBeatifulSoup4でコメントを表すクラス。
https://github.com/waylan/beautifulsoup/blob/master/bs4/element.py#L746L749
つまりCommentオブジェクトであるということが条件になっている。
次にstring引数について調べてみる。
https://www.crummy.com/software/BeautifulSoup/bs4/doc/#the-string-argument
With string you can search for strings instead of tags. As with name and the keyword arguments, you can pass in a string, a regular expression, a list, a function, or the value True.
正規表現やリスト、関数を渡すことができるとある。無名関数を渡しているのはそのためだった。
変数textがどのような値なのか気になって仕方ないので、普通の関数にして確認してみる。
from bs4 import BeautifulSoup as bs from bs4 import Comment def is_comment(text): print(text, type(text) return isinstance(text, Comment) markup = """ <body> <h1>First Heading</h1> <p>This is paragraph.</p> <p>spam ham eggs</p> <!-- This is comment --> <!-- foo bar baz --> </body> """ soup = bs(markup, 'html.parser') comments = soup.find_all(string=is_comment)
<class 'bs4.element.NavigableString'> <class 'bs4.element.NavigableString'> First Heading <class 'bs4.element.NavigableString'> <class 'bs4.element.NavigableString'> This is paragraph. <class 'bs4.element.NavigableString'> <class 'bs4.element.NavigableString'> spam ham eggs <class 'bs4.element.NavigableString'> <class 'bs4.element.NavigableString'> This is comment <class 'bs4.element.Comment'> <class 'bs4.element.NavigableString'> foo bar baz <class 'bs4.element.Comment'> <class 'bs4.element.NavigableString'> <class 'bs4.element.NavigableString'>
見づらいうえに改行も認識されてる(´・ω・`)
h1タグやpタグはNavigableString オブジェクト、コメントタグはCommentオブジェクトということがわかった。
ついでにパースしたHTMLの各行に対して処理が行われていることも実感できた。
まとめると、
find_allメソッドのstring引数には関数を渡すことができるパースしたHTMLの各行が、タグの場合は
NavigableString、コメントであればCommentオブジェクトになるstring関数に各行がCommentオブジェクトであることを判定する関数を渡すことで、コメントだけを取得できる
ひとまず腹落ちしたので心置きなくHTMLからコメントが取得できるようになった。
数や順番が統一されていないサークル情報をどう使うかを考えないとならないけど、それは別の話になる。