对于提取特定的嵌套元素,我通常更喜欢使用.select,它使用css selectors(bs4 似乎不支持xpath,但你也可以查看these solutions using the lxml library),所以对于你的情况你可以使用类似的东西
soup.select_one('table[id="form1:tableEx1"]').select('tbody tr')
但结果可能是look a bit weird,因为列可能没有分开 - 要分开列/单元格,你可以get the of rows as tuples 代替
tableRows = [
tuple([c.text.strip() for c in r.find_all(['th', 'td'])]) for r
in BeautifulSoup(tHtml).select_one(
'table[id="form1:tableEx1"]'
).select('tbody tr')
]
(请注意,当 id 包含“:”时,您不能使用 .select(#id) 格式。)
作为提到的cmets之一,您可以使用pandas.read_html(htmlString)到get a list of tables in the html;如果您想要一个特定的表,请使用 attrs 参数:
# import pandas
pandas.read_html(htmlString, attrs={'id': 'form1:tableEx1'})[0]
但你会得到所有的表 - 不仅仅是 tbody 中的内容;这将展平任何嵌套在内部的表格(请参阅results 使用来自this example 的表格)。
我最初使用select 展示的单语句方法根本不能用于嵌套表,因为输出会被打乱。相反,如果你想保留任何嵌套的内部表而不展平,并且如果你可能经常刮表,我有以下一组通常可以使用的函数:
def linkAncestor(t, a=None):
aList = []
while t.parent != a or a is None:
t = t.parent
if t is None:
if a is not None: aList = None
break
aList.append(t.name)
return aList
def getStrings_table(xSoup):
# not perfect, but enough for me so far
tableTags = ['table', 'tr', 'th', 'td']
return "
".join([
c.get_text(strip=True) for c in xSoup.children
if c.get_text(strip=True) and (c.name is None or (
c.name not in tableTags and not c.find(tableTags)
))
])
- 然后,您可以定义将表格提取为 python 字典的函数:
def tablesFromSoup(mSoup, mode='a', simpleOp=False):
typeDict = {'t': 'table', 'r': 'row', 'c': 'cell'}
finderDict = {'t': 'table', 'r': 'tr', 'c': ['th', 'td']}
refDict = {
'a': {'tables': 't', 'loose_rows': 'r', 'loose_cells': 'c'},
't': {'inner_tables': 't', 'rows': 'r', 'loose_cells': 'c'},
'r': {'inner_tables': 't', 'inner_rows': 'r', 'cells': 'c'},
'c': {'inner_tables': 't', 'inner_rows': 'r', 'inner_cells': 'c'}
}
mode = mode if mode in refDict else 'a'
# for when simpleOp = True
nextModes = {'a': 't', 't': 'r', 'r': 'c', 'c': 'a'}
mainCont = {
'a': 'tables', 't': 'rows', 'r': 'cells', 'c': 'inner_tables'
}
innerContent = {}
for k in refDict[mode]:
if simpleOp and k != mainCont[mode]:
continue
fdKey = refDict[mode][k] # also the mode for recursive call
innerSoups = [(
s, linkAncestor(s, mSoup)
) for s in mSoup.find_all(finderDict[fdKey])]
innerSoups = [s for s in innerSoups if not (
'table' in s[1] or 'tr' in s[1] or 'td' in s[1] or 'th' in s[1]
)]
# recursive call
kCont = [tablesFromSoup(s[0], fdKey, simpleOp) for s in innerSoups]
if simpleOp:
if kCont == [] and mode == 'c':
break
return tuple(kCont) if mode == 'r' else kCont
# if not empty, check if header then add to output
if kCont:
if 'row' in k:
for i in range(len(kCont)):
if 'isHeader' in kCont[i]: continue
kCont[i]['isHeader'] = 'thead' in innerSoups[i][1]
if 'cell' in k:
isH = [(c[0].name == 'th' or 'thead' in c[1]) for c in innerSoups]
if sum(isH) > 0:
if mode == 'r':
innerContent['isHeader'] = True
else:
innerContent[f'isHeader_{k}'] = isH
innerContent[k] = kCont
if innerContent == {} and mode == 'c':
innerContent = mSoup.text.strip()#.get_text(strip=True)
elif mode in typeDict:
if innerContent == {}:
innerContent['innerText'] = mSoup.get_text(strip=True)
else:
innerStrings = getStrings_table(mSoup)
if innerStrings:
innerContent['stringContent'] = innerStrings
innerContent['type'] = typeDict[mode]
return innerContent
和之前一样example,这个函数给this output;如果simpleOp 参数设置为True,则结果为simpler output,但随后不再区分标头并且还排除了一些其他外围数据。