【问题标题】:Scraping Data from a Tableau Map从 Tableau 地图中抓取数据
【发布时间】:2020-11-11 11:16:57
【问题描述】:

我正在尝试获取伊利诺伊州纳洛酮配送中心的位置和名称,以开展关于阿片类药物危机的研究项目。

公共卫生部https://idph.illinois.gov/OpioidDataDashboard/可以从这里访问这个由画面生成的仪表板

我已经尝试了我能找到的一切。首先使用 Tableau 的界面更改 url 以“下载”数据。那只能让我下载一个 pdf 地图,而不是它背后的实际数据集。其次,我修改了我在 Stack Overflow 上见过几次的 python 脚本来尝试请求数据。但是,我认为它遇到了某种错误。代码如下。

url = "https://interactive.data.illinois.gov/t/DPH/views/opioidTDWEB_prod/NaloxoneDistributionLocations"

r = requests.get(
    url,
    params= {
        ":embed":"y",
        ":showAppBanner":"false",
        ":showShareOptions":"true",
        ":display_count":"no",
        "showVizHome": "no"
    }
)
soup = BeautifulSoup(r.text, "html.parser")
print(soup)
tableauData = json.loads(soup.find("textarea",{"id": "tsConfigContainer"}).text)

dataUrl = f'https://tableau.ons.org.br{tableauData["vizql_root"]}/bootstrapSession/sessions/{tableauData["sessionid"]}'

r = requests.post(dataUrl, data= {
    "sheet_id": tableauData["sheetId"],
})

dataReg = re.search('\d+;({.*})\d+;({.*})', r.text, re.MULTILINE)
info = json.loads(dataReg.group(1))
data = json.loads(dataReg.group(2))

print(data["secondaryInfo"]["presModelMap"]["dataDictionary"]["presModelHolder"]["genDataDictionaryPresModel"]["dataSegments"]["0"]["dataColumns"])

感谢任何帮助。

【问题讨论】:

    标签: python web-scraping tableau-api screen-scraping


    【解决方案1】:

    编辑

    我创建了一个tableau scraper library 来将工作表数据提取到熊猫数据框中。

    代码更简单,但在您的情况下,您仍然需要使用 xsrf 令牌构建 URL:

    from tableauscraper import TableauScraper as TS
    import requests
    from bs4 import BeautifulSoup
    
    init_url = "https://idph.illinois.gov/OpioidDataDashboard/"
    r = requests.get(init_url)
    soup = BeautifulSoup(r.text, "html.parser")
    paramTags = dict([
        (t["name"], t["value"])
        for t in soup.find("div", {"class": "tableauPlaceholder"}).findAll("param")
    ])
    
    url = f'{paramTags["host_url"]}trusted/{paramTags["ticket"]}{paramTags["site_root"]}/views/{paramTags["name"]}'
    
    ts = TS()
    ts.loads(url)
    dashboard = ts.getWorkbook()
    
    for t in dashboard.worksheets:
        # show worksheet name
        print(f"WORKSHEET NAME : {t.name}")
        # show dataframe for this worksheet
        print(t.data)
    

    Try this on repl.it


    原帖

    这有点复杂,因为有以下几种组合:

    • 存在 tsconfig textarea 的画面“配置页面”不是原始页面的一部分。该 url 是从一些 param html 标签动态构建的
    • 它在 cookie 中使用了一个交叉伪造令牌,但为了获取该 cookie,您需要调用一个特定的 api,其 url 是从一些 param html 标记动态构建的
    • 通过 tsconfig 参数,我们可以构建数据 url,正如您在其他 stackoverflow 帖子中找到的那样,例如 thisthisthis

    流程如下:

    • 调用GET https://idph.illinois.gov/OpioidDataDashboard/,在类tableauPlaceholder的div下抓取param标签

    从那里主机是:https://interactive.data.illinois.gov

    • 从以前的param 标签,构建如下所示的“会话 URL”:

      GET /trusted/{ticket}/t/DPH/views/opioidTDWEB_prod/MortalityandMorbidity
      

    上面的 url 将仅用于存储 cookie(包括 cookie 中的 xsrf 令牌)

    • 从之前的 param 标签,构建如下所示的“配置 URL”:

      GET /t/DPH/views/opioidTDWEB_prod/MortalityandMorbidity
      

    提取id为tsConfigContainer的textarea并从中解析json

    • 从上面提取的 json 中构建“数据 url”,url 如下所示:

      POST /vizql/t/DPH/w/opioidTDWEB_prod/v/MortalityandMorbidity/bootstrapSession/sessions/{session_id}
      

    然后你有一个 json 响应,前面有一些字符串以防止json hijacking。你需要正则表达式来提取它,然后解析巨大的 json 数据

    所需的所有网址都是这样的:

    GET https://idph.illinois.gov/OpioidDataDashboard/
    GET https://interactive.data.illinois.gov/trusted/yIm7jkXyRQuH9Ff1oPvz_w==:790xMcZuwmnvijXHg6ymRTrU/t/DPH/views/opioidTDWEB_prod/MortalityandMorbidity
    GET https://interactive.data.illinois.gov/t/DPH/views/opioidTDWEB_prod/MortalityandMorbidity
    POST https://interactive.data.illinois.gov/vizql/t/DPH/w/opioidTDWEB_prod/v/MortalityandMorbidity/bootstrapSession/sessions/2A3E3BA96A6C4E65B36AEDB4A536D09F-1:0
    

    完整代码:

    import requests
    from bs4 import BeautifulSoup
    import json
    import re
    
    s = requests.Session()
    
    init_url = "https://idph.illinois.gov/OpioidDataDashboard/"
    print(f"GET {init_url}")
    r = s.get(init_url)
    soup = BeautifulSoup(r.text, "html.parser")
    paramTags = dict([
        (t["name"], t["value"]) 
        for t in soup.find("div", {"class":"tableauPlaceholder"}).findAll("param")
    ])
    
    # get xsrf cookie
    session_url = f'{paramTags["host_url"]}trusted/{paramTags["ticket"]}{paramTags["site_root"]}/views/{paramTags["name"]}'
    print(f"GET {session_url}")
    r = s.get(session_url)
    
    config_url = f'{paramTags["host_url"][:-1]}{paramTags["site_root"]}/views/{paramTags["name"]}'
    print(f"GET {config_url}")
    r = s.get(config_url,
        params = {
            ":embed": "y",
            ":showVizHome": "no",
            ":host_url": "https://interactive.data.illinois.gov/",
            ":embed_code_version": 2,
            ":tabs": "yes",
            ":toolbar": "no",
            ":showShareOptions": "false",
            ":display_spinner": "no",
            ":loadOrderID": 0,
    })
    soup = BeautifulSoup(r.text, "html.parser")
    tableauData = json.loads(soup.find("textarea",{"id": "tsConfigContainer"}).text)
    
    dataUrl = f'{paramTags["host_url"][:-1]}{tableauData["vizql_root"]}/bootstrapSession/sessions/{tableauData["sessionid"]}'
    print(f"POST {dataUrl}")
    r = s.post(dataUrl, data= {
        "sheet_id": tableauData["sheetId"],
    })
    dataReg = re.search('\d+;({.*})\d+;({.*})', r.text, re.MULTILINE)
    info = json.loads(dataReg.group(1))
    data = json.loads(dataReg.group(2))
    
    print(data["secondaryInfo"]["presModelMap"]["dataDictionary"]["presModelHolder"]["genDataDictionaryPresModel"]["dataSegments"]["0"]["dataColumns"])
    

    Try this on repl.it

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-01-15
      • 1970-01-01
      • 2023-02-05
      • 2015-08-12
      • 2018-10-14
      • 1970-01-01
      相关资源
      最近更新 更多