オレとChatGPT

AIとの会話記録

HTMLテーブルをListに変換

オレ:

BeautifulSoup を使って HTML の

で表したデータをPython の List の List に変換する関数を作成してください。

  • <td> もしくは <th> で囲まれた中身だけをデータとします。中身は get_text(strip=True) で取得する事とします。

  • colspan と rowspan を処理します例えば

<table>
     <tr><td colspan="2" rowspan="2">10</td><td>20</tr>
  <tr><td>30</td></tr>
  <tr><td>40</td><td>50</td><td>60</td>
</table>

[[10, None, 20][None, None, 30][40, 50, 60]]

のようなデータに変換されます。


ChatGPT:GPT-4

BeautifulSoupを使用してHTMLの<table>PythonのListのListに変換する関数を作成します。この関数は、<td><th>タグの中身を取得し、colspanおよびrowspan属性を考慮します。以下にPythonコードを示します。

from bs4 import BeautifulSoup

def table_to_list(html):
    # HTMLをBeautifulSoupオブジェクトにパースする
    soup = BeautifulSoup(html, 'html.parser')
    
    # 最初の<table>タグを取得する
    table = soup.find('table')
    
    # 結果を格納するリスト
    result = []
    
    # rowspanの状態を管理するリスト
    rowspans = []
    
    # 各行を処理する
    for row in table.find_all('tr'):
        # 現在の行のデータを格納するリスト
        current_row = []
        # 現在のセルのインデックス
        cell_index = 0
        
        # rowspanが前の行から続いている場合、それを処理する
        for r in rowspans:
            current_row.append(None)
            cell_index += 1
            r[1] -= 1
            if r[1] == 0:
                rowspans.remove(r)
                
        # 各セルを処理する
        for cell in row.find_all(['td', 'th']):
            # rowspanとcolspanを取得する
            rowspan = int(cell.get('rowspan', 1))
            colspan = int(cell.get('colspan', 1))
            
            # セルのテキストを取得する
            text = cell.get_text(strip=True)
            
            # rowspanが1より大きい場合、rowspansリストに追加する
            if rowspan > 1:
                rowspans.append([cell_index, rowspan-1])
                
            # colspanに応じてデータを追加する
            for i in range(colspan):
                current_row.append(text if i == 0 else None)
                cell_index += 1
                
        # 結果に現在の行を追加する
        result.append(current_row)
    
    return result

# HTMLの文字列
html = """
<table>
    <tr><td colspan="2" rowspan="2">10</td><td>20</td></tr>
    <tr><td>30</td></tr>
    <tr><td>40</td><td>50</td><td>60</td></tr>
</table>
"""

# 関数を呼び出して結果を出力する
print(table_to_list(html))

このコードは、HTMLテーブルを解析し、<td>および<th>タグの中身を取得してPythonのリストに変換します。また、colspanおよびrowspan属性を適切に処理します。


オレ:

上記のコードは例えば次のような table を正しく処理できません。

<table>
    <tr><td rowspan="2">10</td><td>20</td><td rowspan="2">30</td></tr>
    <tr><td>40</td></tr>
    <tr><td>50</td><td>60</td><td>70</td></tr>
</table>

rowspan, colspan がある場合 2次元的にどのセルが使用されているかを記録して判定する必要があります。


ChatGPT:GPT-4

おっしゃる通りです。rowspancolspanの属性を考慮して、2次元配列でどのセルが使用されているかを記録する必要があります。これにより、テーブル内の各セルが正確に配置されます。 以下は、この考慮を含めた改良された関数です:

from bs4 import BeautifulSoup

def table_to_list(html):
    # HTMLをBeautifulSoupオブジェクトにパースする
    soup = BeautifulSoup(html, 'html.parser')
    
    # 最初の<table>タグを取得する
    table = soup.find('table')
    
    # 結果を格納するリスト
    result = []
    
    # 各行を処理する
    for i, row in enumerate(table.find_all('tr')):
        # 既存の行がない場合は追加する
        while len(result) <= i:
            result.append([])
        
        # 現在のセルのインデックス
        cell_index = 0
        
        # 各セルを処理する
        for cell in row.find_all(['td', 'th']):
            # rowspanとcolspanを取得する
            rowspan = int(cell.get('rowspan', 1))
            colspan = int(cell.get('colspan', 1))
            
            # セルのテキストを取得する
            text = cell.get_text(strip=True)
            
            # 既に埋まっているセルをスキップする
            while len(result[i]) > cell_index and result[i][cell_index] is not None:
                cell_index += 1
            
            # rowspanおよびcolspanに基づいてデータを配置する
            for r in range(rowspan):
                for c in range(colspan):
                    row_idx = i + r
                    col_idx = cell_index + c
                    
                    # 必要なら行を追加する
                    while len(result) <= row_idx:
                        result.append([])
                    
                    # 必要なら列を追加する
                    while len(result[row_idx]) <= col_idx:
                        result[row_idx].append(None)
                    
                    # 最初のセルにテキストを配置し、それ以外はNoneにする
                    result[row_idx][col_idx] = text if r == 0 and c == 0 else None
            
            # インデックスを更新する
            cell_index += 1
    
    return result

# HTMLの文字列
html = """
<table>
    <tr><td rowspan="2">10</td><td>20</td><td rowspan="2">30</td></tr>
    <tr><td>40</td></tr>
    <tr><td>50</td><td>60</td><td>70</td></tr>
</table>
"""

# 関数を呼び出して結果を出力する
print(table_to_list(html))

このコードでは、テーブル内の各セルの位置を2次元配列で記録し、rowspanおよびcolspan属性を考慮して適切に配置します。


オレ:

これはダメでです。一行目の <tr><td rowspan="2">10</td><td>20</td><td rowspan="2">30</td></tr> を処理した後、 result は '10', '20', '30'], [None, None, None]] となります。後に値を入れる事ができる場所と値を入れられない場所が区別されません。 値を入れられる場所と入れらい場所は区別する必要があります。幸いデータはテキスト型のみなので、テキストでなく None でもなり値を使えば区別できます。


ChatGPT:GPT-4

確かに、区別するために特別な値を使用する必要があります。Noneは値が存在しないことを示すため、異なるマーカーを使用して、セルが使用不可であることを示します。以下は、この考慮を含めた改良された関数です:

from bs4 import BeautifulSoup

def table_to_list(html):
    # HTMLをBeautifulSoupオブジェクトにパースする
    soup = BeautifulSoup(html, 'html.parser')
    
    # 最初の<table>タグを取得する
    table = soup.find('table')
    
    # 結果を格納するリスト
    result = []
    
    # 使用不可のセルを示すマーカー
    unavailable = object()
    
    # 各行を処理する
    for i, row in enumerate(table.find_all('tr')):
        # 既存の行がない場合は追加する
        while len(result) <= i:
            result.append([])
        
        # 現在のセルのインデックス
        cell_index = 0
        
        # 各セルを処理する
        for cell in row.find_all(['td', 'th']):
            # rowspanとcolspanを取得する
            rowspan = int(cell.get('rowspan', 1))
            colspan = int(cell.get('colspan', 1))
            
            # セルのテキストを取得する
            text = cell.get_text(strip=True)
            
            # 使用中または使用不可のセルをスキップする
            while len(result[i]) > cell_index and result[i][cell_index] in (unavailable, None):
                cell_index += 1
            
            # rowspanおよびcolspanに基づいてデータを配置する
            for r in range(rowspan):
                for c in range(colspan):
                    row_idx = i + r
                    col_idx = cell_index + c
                    
                    # 必要なら行を追加する
                    while len(result) <= row_idx:
                        result.append([])
                    
                    # 必要なら列を追加する
                    while len(result[row_idx]) <= col_idx:
                        result[row_idx].append(None)
                    
                    # 最初のセルにテキストを配置し、それ以外は使用不可マーカーにする
                    result[row_idx][col_idx] = text if r == 0 and c == 0 else unavailable
            
            # インデックスを更新する
            cell_index += 1
    
    # 使用不可マーカーをNoneに変換する
    for row in result:
        for i, cell in enumerate(row):
            if cell is unavailable:
                row[i] = None
    
    return result

# HTMLの文字列
html = """
<table>
    <tr><td rowspan="2">10</td><td>20</td><td rowspan="2">30</td></tr>
    <tr><td>40</td></tr>
    <tr><td>50</td><td>60</td><td>70</td></tr>
</table>
"""

# 関数を呼び出して結果を出力する
print(table_to_list(html))

このコードでは、unavailableという特別なマーカーを使用して、セルが使用不可であることを示します。これにより、Noneと使用不可のセルを区別することができます。関数の最後で、このマーカーをNoneに置き換えて最終的な結果を得ます。 これにより、rowspanおよびcolspan属性を持つテーブルを正確に処理することができます。


オレ:

ここの部分がおかしいです。せっかく unavailable を用意したのに None までスキップしています。 None はスキップする必要は無いです。

# 使用中または使用不可のセルをスキップする
while len(result[i]) > cell_index and result[i][cell_index] in (unavailable, None):
    cell_index += 1

ChatGPT:GPT-4

ごめんなさい、おっしゃる通りです。Noneをスキップする必要はありません。以下は修正されたコードです。

from bs4 import BeautifulSoup

def table_to_list(html):
    # HTMLをBeautifulSoupオブジェクトにパースする
    soup = BeautifulSoup(html, 'html.parser')
    
    # 最初の<table>タグを取得する
    table = soup.find('table')
    
    # 結果を格納するリスト
    result = []
    
    # 使用不可のセルを示すマーカー
    unavailable = object()
    
    # 各行を処理する
    for i, row in enumerate(table.find_all('tr')):
        # 既存の行がない場合は追加する
        while len(result) <= i:
            result.append([])
        
        # 現在のセルのインデックス
        cell_index = 0
        
        # 各セルを処理する
        for cell in row.find_all(['td', 'th']):
            # rowspanとcolspanを取得する
            rowspan = int(cell.get('rowspan', 1))
            colspan = int(cell.get('colspan', 1))
            
            # セルのテキストを取得する
            text = cell.get_text(strip=True)
            
            # 使用中のセルをスキップする
            while len(result[i]) > cell_index and result[i][cell_index] == unavailable:
                cell_index += 1
            
            # rowspanおよびcolspanに基づいてデータを配置する
            for r in range(rowspan):
                for c in range(colspan):
                    row_idx = i + r
                    col_idx = cell_index + c
                    
                    # 必要なら行を追加する
                    while len(result) <= row_idx:
                        result.append([])
                    
                    # 必要なら列を追加する
                    while len(result[row_idx]) <= col_idx:
                        result[row_idx].append(None)
                    
                    # 最初のセルにテキストを配置し、それ以外は使用不可マーカーにする
                    result[row_idx][col_idx] = text if r == 0 and c == 0 else unavailable
            
            # インデックスを更新する
            cell_index += 1
    
    # 使用不可マーカーをNoneに変換する
    for row in result:
        for i, cell in enumerate(row):
            if cell is unavailable:
                row[i] = None
    
    return result

# HTMLの文字列
html = """
<table>
    <tr><td rowspan="2">10</td><td>20</td><td rowspan="2">30</td></tr>
    <tr><td>40</td></tr>
    <tr><td>50</td><td>60</td><td>70</td></tr>
</table>
"""

# 関数を呼び出して結果を出力する
print(table_to_list(html))

上記の修正では、Noneはスキップせず、unavailableマーカーがある場所のみをスキップしています。これにより、rowspanおよびcolspan属性を持つテーブルのセルを正確に処理することができます。 この修正により、Noneは使用可能なセルを示すために使用され、unavailableマーカーはrowspanまたはcolspan属性によって占有されたセルを示します。関数の最後で、unavailableマーカーをNoneに置き換えることで、最終的なリストが完成します。


感想

まだ、細かい部分や場合によっては正しく処理できない気がしますが、当面の用は足りるのでここで終わりとしました。

pandas の read_html で

<table>
    <tr><td colspan="2" rowspan="2">10</td><td>20</td></tr>
    <tr><td>30</td></tr>
    <tr><td>40</td><td>50</td><td>60</td></tr>
</table>

みたいなテーブルを読むと [[10, 10, 20][10, 10, 30][40, 50, 60]] のようになります。これはこれで有用なのですが、欲しかったのは [[10, None, 20][None, None, 30][40, 50, 60]] なので自前で実装したかのですが。めんどくさいのでChatGPT に手伝ってもらいました。

結果リテイクに手間が取られて自分で実装するのと比較して微妙なのですが、やる気が出ない時は役立ちます。

ちなみに後で Bard と Bing にリテイクなしで聞いてみましたが、Bing は colspan rowspan を全く無視した回答、Bingもあまり役立つコードでは無かったです。