使用 Pyppeteer 通过 JavaScript 从 Vanguard 下载 CSV / Excel 文件

Using Pyppeteer to download CSV / Excel file from Vanguard via JavaScript

我正在尝试自动从 Web 下载 Vanguard 基金的持股。 links 通过 JavaScript 解析,所以我正在使用 Pyppeteer 但我没有获取文件。请注意,link 表示 CSV 但它提供了一个 Excel 文件。

在我的浏览器中它是这样工作的:

  1. 去基金URL,例如 https://www.vanguard.com.au/personal/products/en/detail/8225/portfolio
  2. 关注link,“查看总持股数”
  3. 单击 link、“导出为 CSV”

我试图在 Python 中复制它。第一个 link 跟随似乎有效,因为我得到了不同的 HTML 但第二次点击给了我相同的页面,而不是下载。

import asyncio
from pyppeteer import launch
import os

async def get_page(browser, url):
    page = await browser.newPage()
    await page.goto(url)
    return page

async def fetch(url):
    browser = await launch(options={'args': ['--no-sandbox']})  #headless=True, 
    page = await get_page(browser, url)     
    await page.waitFor(2000)
    
    # save the page so we can see the source
    wkg_dir = 'vanguard'
    t_file = os.path.join(wkg_dir,'8225.htm')
    with open(t_file, 'w', encoding="utf-8") as ef:
        ef.write(await page.content())
    
    accept = await page.xpath('//a[contains(., "See count total holdings")]')
    print(f'Found {len(accept)} "See count total holdings" links')
    
    if accept:
        await accept[0].click()
        await page.waitFor(2000)
    else:
        print('DID NOT FIND THE LINK')
        return False
    
    # save the pop-up page for debug
    t_file = os.path.join(wkg_dir,'first_page.htm')
    with open(t_file, 'w', encoding="utf-8") as ef:
        ef.write(await page.content())
    
    links = await page.xpath('//a[contains(., "Export to CSV")]')
    print(f'Found {len(links)} "Export to CSV" links')    # 3 of these
    for i, link in enumerate(links):
        print(f'Trying link {i}')
        await link.click()
        await page.waitFor(2000)
        
        t_file = os.path.join(wkg_dir,f'csv_page{i}.csv')
        with open(t_file, 'w', encoding="utf-8") as ef:
            ef.write(await page.content())

    return True

#---------- Main ------------
# Set constants and global variables
url = 'https://www.vanguard.com.au/personal/products/en/detail/8225/portfolio'
loop = asyncio.get_event_loop()
status = loop.run_until_complete(fetch(url))

很想听听任何熟悉 Puppeteer / Pyppeteer 的人的建议。

首先,page.waitFor(2000)应该是不得已而为之。这是一种竞争条件,最坏的情况下会导致假阴性,最好的情况下会减慢您的抓取速度。我推荐 page.waitForXPath,它会产生一个紧密的轮询循环,以便在 xpath 可用后立即继续您的代码。

关于元素选择的主题,我会在您的 xpath 中使用 text() 而不是更精确的 .

我不确定 ef.write(await page.content()) 是如何为您工作的——应该只提供页面 HTML,而不是 XLSX 下载。 link 单击会通过对话框触发下载。接受此下载涉及启用 Chrome 下载

await page._client.send("Page.setDownloadBehavior", {
  "behavior": "allow", 
  "downloadPath": r"C:\Users\you\Desktop" # TODO set your path
})

下一个障碍是 bypassing or suppressing the "multiple downloads" permission prompt 当您尝试在同一页面上下载多个文件时显示 Chrome。我无法弄清楚如何阻止它,所以我的代码只是导航到每个 link 的页面作为解决方法。我会将我的解决方案保留为次优但实用的解决方案,让其他人(或我未来的自己)改进它。

顺便说一下,索引 1 和 2 处的两个 XLSX 文件似乎是相同的。这段代码无论如何都会下载所有 3 个,但您可以跳过最后一个,具体取决于页面是否随时间变化——我对此并不熟悉。

我正在使用 ,使用浏览器控制台的点击而不是 Puppeteer:page.evaluate("e => e.click()", csv_link)

这是我的尝试:

import asyncio
from pyppeteer import launch

async def get_csv_links(page):
    await page.goto(url)

    xp = '//a[contains(text(), "See count total holdings")]'
    await page.waitForXPath(xp)
    accept, = await page.xpath(xp)
    await accept.click()

    xp = '//a[contains(text(), "Export to CSV")]'
    await page.waitForXPath(xp)
    return await page.xpath(xp)

async def fetch(url):
    browser = await launch(headless=False)
    page, = await browser.pages()
    await page._client.send("Page.setDownloadBehavior", {
        "behavior": "allow", 
        "downloadPath": r"C:\Users\you\Desktop" # TODO set your path
    })
    csv_links = await get_csv_links(page)

    for i in range(len(csv_links)):
        # open a fresh page each time as a hack to avoid multiple file prompts
        csv_link = (await get_csv_links(page))[i]
        await page.evaluate("e => e.click()", csv_link)

        # let download finish; this is a race condition
        await page.waitFor(3000)

if __name__ == "__main__":
    url = "https://www.vanguard.com.au/personal/products/en/detail/8225/portfolio"
    asyncio.get_event_loop().run_until_complete(fetch(url))

改进说明:

  • 尝试 --enable-parallel-downloading 之类的参数或 'profile.default_content_setting_values.automatic_downloads': 1 之类的设置来抑制“多次下载”警告。
  • 算出 以便删除最后的 waitFor(3000)。这里的另一种选择可能涉及轮询您期望的文件;您可以访问 linked 主题以获取想法。
  • 弄清楚如何

后代的其他资源: