我找不到使用 bs4 的 table,我找到了使用 `re` 的替代方法,但我不确定如何获取我需要的信息

I can't find a table using bs4, and I found an alternative using `re`, but I'm not sure how to get the information I need

我想创建一个字典,将 holdings 作为键,将 Weight(%) 作为值。但是当我尝试使用 soup.find('table', {'id' : 'etf_holding_table'}) 访问 table 时,没有任何显示。我看到一些帖子说它可能在评论中,并试图复制那里的一些方法,但我没能成功。

我最终找到了某人对使用 re 提取代码信息的方法的回应,但我似乎找不到任何好的资源来解释他的代码在做什么。
Here is the post I copied code from to get the ticker.

import requests
import re

keys = ['ARKK']
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0"}
url = 'https://www.zacks.com/funds/etf/ARKK/holding'

with requests.Session() as req:
        req.headers.update(headers)
        for key in keys:
            r = req.get(url.format(key))
            goal = re.findall(r'etf\\/(.*?)\', r.text)
            print(goal)
OUTPUT
['TSLA', 'TDOC', 'COIN', 'ROKU', 'U', 'ZM', 'SPOT', 'SQ', 'SHOP', 'PATH', 'TWLO', 'EXAS', 'NTLA', 'Z', 'PLTR', 'CRSP', 'TWTR', 'BEAM', 'DKNG', 'NVTA', 'FATE', 'TXG', 'DOCU', 'HOOD', 'PACB', 'PD', 'IRDM', 'TSP', 'DNA', 'TWST', 'VCYT', 'SGFY', 'SKLZ', 'EDIT', 'TWOU', 'IOVA', 'SSYS', 'TRMB', 'BLI', 'MTLS', 'CERS', 'CGEN', 'PRLB', 'NSTG']

通过对 re.findall() 进行一些尝试,我能够获得我想要的信息(以黄色突出显示),但我不确定如何获得我现在需要的数字。

我非常感谢一些关于理解和使用 re 的好资源,因为我显然不太了解我在做什么,或者如何获取我需要的信息。我会向从中获取代码的发帖者发送消息,但似乎您无法向 Stack Overflow 上的某人发送消息。

数据包含在“javascript 变量”etf_holdings.formatted_data

<script>
var etf_holdings            = {};
etf_holdings.formatted_data = [ [ "TESLA INC", ...

这在您的浏览器中由 javascript 处理并变成您看到的 table。

当您使用 requests 获取原始 html 时 - 您没有执行 javascript - 这就是为什么 table 在您尝试时“不存在”的原因用 BeautifulSoup.

找到它

隔离包含数据的行的一种方法:

>>> import json, requests
>>> r = requests.get('https://www.zacks.com/funds/etf/ARKK/holding', headers={'User-Agent': ''})
>>> line = next(line for line in r.text.splitlines() if line.startswith('etf_holdings.formatted_data '))

如果我们保留 [ [ .... ] ] 即删除第一个 = 之前的所有内容并切掉尾随的分号 - 我们可以使用 json 模块加载它。

>>> line[:40]
'etf_holdings.formatted_data = [ [ "TESLA'
>>> line[-10:]
'</a>" ] ];'

.find() 切片是一种方法:

>>> line = line[line.find('= ') + 2:-1]
>>> data = json.loads(line)
>>> len(data)
45

每一项都是用于在 table 中创建一行的原始数据:

>>> data[0]
['TESLA INC',
 '<button class="modal_external appear-on-focus" 
    href="/modals/quick-quote.php" rel="TSLA">TSLA Quick Quote</button>
    <a href="//www.zacks.com/funds/etf/TSLA" rel="TSLA" 
    class=" hoverquote-container-od " show-add-portfolio="true" >
    <span class="hoverquote-symbol">TSLA<span class="sr-only"></span></span></a>',
 '2,073,604',
 '11.18',
 '8.48',
 '<a class="report_document newwin" 
   href="/zer/report/TSLA" alt="View Report" title="View Report"></a>']

因此,正如已经指出的那样,数据位于响应中的不同 JavaScript 变量中。

因此,我会更改正则表达式以获取整个 JavaScript 数组对象,然后将其传递给 json 库以提取所需的信息。

正则表达式为:

etf_holdings\.formatted_data = (\[.*?\]);

即匹配如下:

为了re-use.

的效率提前编译这个模式

使用.search()匹配字符串中的任意位置,然后提取第一个匹配组。

用json解析提取的组,得到如下列表列表:

请注意,您需要的数据包含在父列表中每个列表的索引 1 和 3 中。索引1是html可以用BeautifulSoup解析得到想要的符号,索引3可以直接提取。对于某些项目,没有 symbol/associated 重量值:

因为这可能导致在抓取过程中解析出相同的键,在最终字典中没有任何意义,我只是通过测试 rel=" 是否存在于索引 1 字符串中来排除这些项目。这是为了避免在列表推导中解析两次 html。

我假设 - 基于检查上述索引 1 字符串,并且这些是高度特定的短字符串 - 这足以准确确定符号的 presence/absence。如果存在,我从索引 1 创建一个 BeautifulSoup 对象,然后提取第一个 rel 属性以获取符号。我将它与索引 3 值配对,在列表推导中的一个元组中,以一个元组列表结束。我用返回的元组列表扩展了一个全局列表,每次都在全局列表 keys.

上循环

最后,我将该元组列表转换为字典。这假设在不同的请求中没有重复的符号。如果全局元组列表中可能存在重复的潜在键,您将需要修改逻辑以捕获母 ETF,然后决定如何处理。

import re
import json
import requests
from bs4 import BeautifulSoup as bs


def main(url):
    with requests.Session() as req:
        req.headers.update(headers)
        for key in keys:
            r = req.get(url.format(key))
            print(f"Extracting: {r.url}")
            data = json.loads(re_object.search(r.text).group(1))
            values.extend([(bs(i[1], 'lxml').select_one('[rel]')['rel'], i[3])
                          for i in data if ' rel="' in i[1]])


keys = ['ARKK', 'VPOP']
headers = {"User-Agent": "Mozilla/5.0"}
values = []
re_object = re.compile(r'etf_holdings\.formatted_data = (\[.*?\]);')

main("https://www.zacks.com/funds/etf/{}/holding")
ticker_weights = dict(values)
print(ticker_weights)