Beautifulsoupの `select` メソッドで取得したものをさらに絞り込む方法を教えていただきたいです。  
以下の `class="A"` の要素を取得したのちに配下の`class="B"` と `class="C"` の要素を絞り込んで取得したいです。

```

    <div class="A">
      <div class="B"> ... </div>
      <div class="C"> ... </div>
    </div>
    
```
CC BY-SA 4.0
2021-09-09T14:35:55.557
82382
2021-09-24T05:05:25.247
2021-09-24T05:05:25.247
3060
48134
question
beautifulsoup
Beautifulsoupの使い方について
`find` または `find_all`をつなげる方法があります。`class`はPythonの予約語なので、 `class_`
で[指定することになっています](https://beautiful-soup-4.readthedocs.io/en/latest/#searching-
by-css-class)。

```

    a = soup.select("div.A")[0]
    a.find_all("div", class_="B") #=> <div class="B"></div>
    
```
CC BY-SA 4.0
2021-09-09T14:53:24.800
82385
2021-09-09T15:11:44.907
2021-09-09T15:11:44.907
7500
7500
82382
answer
以下は `bs4.BeautifulSoup.select` を使う方法です。

```

    from bs4 import BeautifulSoup
    
    data = '''
    <div class='A'>
      <div class='B'></div>
      <div class='C'></div>
      <div class='D'></div>
      <div class='E'></div>
    </div>
    '''
    
    soup = BeautifulSoup(data, features="html.parser")
    selection = soup.select('div.A > div.B, div.C')
    
    print(selection)
    
    =>
    [<div class="B"></div>, <div class="C"></div>]
    
```

~~> azure.microsoft.com/ja-jp/services になります。 > 次のような順での全てのサービスの情報を取得したいです。 >
> ・項番、カテゴリ名、カテゴリ概要、サービス名、サービス概要 > > 例） > > 1、AI＋機械学習、あらゆる開発者〜、Anomaly
Detector、異常検出機能を〜 > 2、AI＋機械学習、あらゆる開発者〜、Azure Bot Service 、顧客向けの〜

```

    from urllib.request import urlopen
    from bs4 import BeautifulSoup
    import pandas as pd
    
    URL = 'https://azure.microsoft.com/ja-jp/services/'
    html = urlopen(URL)
    bs = BeautifulSoup(html, "html.parser")
    
    # selection
    selected = bs.select(
      '.product-category, .product-category + p, h3.text-heading5 span, h3.text-heading5 + p'
    )
    
    # filtering
    n, tbl = 1, []
    for item in selected:
      val = item.text
      if item.has_attr('class'):
        cls = item.attrs['class'][0]
        if cls == 'product-category':
          category = val
          continue
        elif cls == 'text-body4':
          service_abstract = val
          tbl.append([n, category, category_abstract, service, service_abstract])
          n += 1
      elif item.name == 'span':
        service = val
      elif item.name == 'p':
        category_abstract = val
    
    # dataframe
    df = pd.DataFrame(
      tbl, columns=[
        '項番', 'カテゴリ名', 'カテゴリ概要', 'サービス名', 'サービス概要'
      ]
    )
    
```

**出力結果**  
`print(df.to_markdown(index=False))`

項番 | カテゴリ名 | カテゴリ概要 | サービス名 | サービス概要  
---|---|---|---|---  
1 | AI + 機械学習 | あらゆる開発者、あらゆるシナリオに適した人工知能の能力を活用して次世代のアプリケーションを作成 | Anomaly
Detector | 異常検出機能をアプリに簡単に追加。  
2 | AI + 機械学習 | あらゆる開発者、あらゆるシナリオに適した人工知能の能力を活用して次世代のアプリケーションを作成 | Azure Bot
Service | 顧客向けの会話型 AI エクスペリエンスを構築する  
3 | AI + 機械学習 | あらゆる開発者、あらゆるシナリオに適した人工知能の能力を活用して次世代のアプリケーションを作成 | Azure
Cognitive Search | モバイルおよび Web アプリ開発のための AI を活用したクラウド検索サービス  
... | ... | ... | ... | ...  
32 | DevOps | シンプルで信頼できる継続的デリバリー ツールを使ってイノベーションを促進する | Azure Artifacts |
パッケージを作成、ホストし、チームで共有  
33 | DevOps | シンプルで信頼できる継続的デリバリー ツールを使ってイノベーションを促進する | Azure Boards |
チームの垣根を超えて作業を計画、追跡したり、作業に関する相談をしたりできます  
34 | DevOps | シンプルで信頼できる継続的デリバリー ツールを使ってイノベーションを促進する | Azure DevOps |
チームがコードを共有し、作業を追跡し、ソフトウェアを出荷するためのサービス  
... | ... | ... | ... | ...  
283 | 分析 | あらゆる種類、量、速度のデータを収集、格納、処理、分析、視覚化する | R Server for HDInsight |
予測分析、機械学習、ビッグ データの統計モデリング  
284 | 分析 | あらゆる種類、量、速度のデータを収集、格納、処理、分析、視覚化する | Azure Purview プレビュー | 統合データ
ガバナンスでビジネス価値を最大化  
285 | 分析 | あらゆる種類、量、速度のデータを収集、格納、処理、分析、視覚化する | Microsoft Graph データ接続 プレビュー |
選択された Microsoft 365 の生産性データセットをお客様の Azure テナントにコピーできるように設計された、安全で高スループットのコネクタ  
  
~~
CC BY-SA 4.0
2021-09-09T15:06:45.753
82386
2021-09-11T09:45:59.717
2021-09-11T09:45:59.717
47127
47127
82382
answer
> Beautifulsoupの select メソッドで取得したものをさらに絞り込む方法  
>  class="A" の要素を取得したのちに

最初に現れる `div`要素であれば  
(そしてそれが `class="A"` であるはずと決まってるなら後者のように記述するかも)

```

    clsa = soup.select('div.A')[0]
    
    #clsa = soup.div   # 個人的にはこっち使うかも
    
```

> 配下のclass="B" と class="C" の要素を絞り込んで

`clsa` の直下であれば `children` 使うのもありかも

```

    for elm in clsa.children:
        print(elm)
    
```

`clsa`直下でなければ, find_all 的な呼び出しで, ぬるく要求するか厳密に要求するか

```

    cls_bc = clsa('div')   # 配下の div
    cls_bc = clsa(class_=True)   # 配下の class属性を持つもの
    cls_bc = clsa('div', class_=True)   # 配下の divで, class属性を持つもの
    
```

そこそこ複雑な条件を満たすものなら関数を用意します (`lambda`でも可)

```

    def has_class_but_no_id(tag):
        return tag.has_attr('class') and not tag.has_attr('id')
    
    cls_bc = clsa(has_class_but_no_id)
    
```

参考:

  * <https://www.crummy.com/software/BeautifulSoup/bs4/doc/>
  * (微妙に古いけど)和訳 <http://kondou.com/BS4/>

* * *

#### 【例】

```

    # 目次ページからアンカー(リンク)取得
    url = 'https://kakuyomu.jp/works/1122334455667788__'    # どこか適当な小説の目次ページ
    r = requests.get(url)
    soup = BeautifulSoup(r.content, 'html.parser')
    
    # 要素(タグ)を辿るとき, (elem).ul.li … のように完全に修飾しなくても
    # (elem).li のように省略可能
    
    links = {li.a['href']: li.a.text
            for li in soup.find(id='table-of-contents')('li')if li.a is not None}
    for p in links:
        title = links[p].strip().split('\n')
        print(f'{urljoin(url, p)}: \n\t{title}')
    
```
CC BY-SA 4.0
2021-09-10T02:21:18.617
82395
2021-09-24T04:57:32.457
2021-09-24T04:57:32.457
43025
43025
82382
answer