89874


[beautifulsoupによるfind_allの利用方法について](https://ja.stackoverflow.com/q/89857/9820)の調査をしたところ、タグ内部の値に対して[`find_all(string=re.compile({regex}))`](https://beautiful-
soup-4.readthedocs.io/en/latest/index.html#the-string-
argument)を使って正規表現検索できることが分かりました。

しかし実際に使用してみたところ、タグの内部に入れ子のタグが存在すると意図通りにマッチングできないケースがありました。  
下記のサンプルコードで、3月,4月が抽出できず、5月が抽出できる理由は何でしょうか。  
また、`string`に正規表現を用いて3月,4月を表示するオプション引数などはありますか？

**サンプルコード**

[code]

    from bs4 import BeautifulSoup
    import re
    
    html = """
    <html>
      <body>
        <p>1月</p>
        <p>2月&lt;hoge/&gt;</p>
        <p>3月<hoge/></p>
        <p>4月<fuga></fuga></p>
        <p><piyo>5月</piyo></p>
        <p>6月ですよ</p>
      </body>
    </html>
    """
    soup = BeautifulSoup(html, "html.parser")
    ps = soup.find_all('p', string=re.compile(r"月"))
    for p in ps:
        print(p.get_text(strip=True))
    
[/code]

**実行結果**

[code]

    1月
    2月<hoge/>
    5月
    6月ですよ
    
[/code]

**バージョン**

winver Windows11 Ver. 21H2(OS Build 2200.739)  
Python 3.10.4  
beautifulsoup4 4.11.1


CC BY-SA 4.0
2022-07-09T14:34:04.990
89870
2022-07-09T20:15:47.490
2022-07-09T14:44:00.517
9820
9820
question
python python3 beautifulsoup
BeautifulSoup4で入れ子のタグがあるとstringの正規表現検索ができないケースがある


> 下記のサンプルコードで、3月,4月が抽出できず、5月が抽出できる理由は何でしょうか。

3月、4月において `.string` が `None` となるためです。この挙動は `p` タグすべてに対して `.string`
の値を調べることで確かめられます。そしていつ `.string` の値が `None`
になるのか、というのは以下のようにドキュメントに書かれています[[1]](https://www.crummy.com/software/BeautifulSoup/bs4/doc/#string)。

これに照らし合わせると、本質問で問題になっている3月と4月は、「タグが複数のものを含んでおり、 `.string` が何を指すかがあいまいになるため
`None` となっている」とわかります。5月のケースでは `p` タグ直下には `piyo` タグしか存在せず、さらに `piyo` タグ直下にも
`5月` という文字列しか存在しないため、抽出が可能となります。

> ##
> .string[[1]](https://www.crummy.com/software/BeautifulSoup/bs4/doc/#string)
>
> If a tag has only one child, and that child is a NavigableString, the child
> is made available as .string:
[code]

>     title_tag.string
>     # 'The Dormouse's story'
>  
[/code]

>
> If a tag’s only child is another tag, and that tag has a .string, then the
> parent tag is considered to have the same .string as its child:
[code]

>     head_tag.contents
>     # [<title>The Dormouse's story</title>]
>  
>     head_tag.string
>     # 'The Dormouse's story'
>  
[/code]

>
> If a tag contains more than one thing, then it’s not clear what .string
> should refer to, so .string is defined to be None:
[code]

>     print(soup.html.string)
>     # None
>  
[/code]

* * *

> また、stringに正規表現を用いて3月,4月を表示するオプション引数などはありますか？

もっと良い方法があるかもしれませんが、走査する各要素を引数として取る以下のような関数を `find_all()`
へ渡すことで3月、4月のパターンにも対応できます。

[code]

    from bs4 import BeautifulSoup
    import re
    
    html = """
    <html>
      <body>
        <p>1月</p>
        <p>2月&lt;hoge/&gt;</p>
        <p>3月<hoge/></p>
        <p>4月<fuga></fuga></p>
        <p><piyo>5月</piyo></p>
        <p>6月ですよ</p>
      </body>
    </html>
    """
    soup = BeautifulSoup(html, "html.parser")
    ps = soup.find_all(lambda tag: tag.name == "p" and re.search(r"月", tag.text))
    for p in ps:
        print(p.get_text(strip=True))
    
[/code]


CC BY-SA 4.0
2022-07-09T20:15:47.490
89874
2022-07-09T20:15:47.490
32986
89870
answer