首页 > 解决方案 > 使用可自定义的下拉列表对表格进行 Python 网络抓取

问题描述

我是一个新手,正在尝试组合我的第一个网页抓取功能,并遇到一些我不完全理解的 HTTP 问题。作为参考,我正在尝试从FanGraphs ZIPS projection page中抓取。

他们的页面默认显示有 30 名玩家的牌桌,但您可以编辑网页,使牌桌每页显示 10、20、30、50、100、500 或 1000 个结果。理想情况下,我会选择每页 500 个结果的选项,但是当我更改下拉菜单时,URL 保持不变,所以我尝试编写一个脚本来获取 HTML 代码并在尝试请求之前编辑 HTML 页面的属性网页的这种变体。(如果您查看页面源代码,则在第 1247 行)

或者,您可以通过页面上的箭头按钮超链接循环浏览其他页面,但它们也不会更改 URL。

我为位置玩家解决了这个问题,因为我可以单独抓取每个位置,然后连接单独的 Pandas 数据帧,但我对 HTTP 请求的工作原理非常无知,并希望得到一些帮助/指导。

我试过类似的东西:

from requests import Request, Session
url = 'https://www.fangraphs.com/projections.aspx?pos=all&stats=pit&type=zips&team=0&lg=al&players=0'
page = requests.get(url)
soup=BeautifulSoup(page.text, 'html.parser')
resultSet = soup.findAll(value=30)
for result in resultSet:
    result["value"]="500"
s = Session()
req = requests.Request('POST', url, data = {'ProjectionBoard1_dg1_ctl00_ctl03_ctl01_PageSizeComboBox_Input value': 500})
resp = s.send(prepped)
print(resp.status_code)

我的想法是抓取结构,编辑配置以匹配我想要的网页,然后反刍固定结构来抓取数据。然后我意识到我不知道自己在做什么。

一些事情:我在这里“甚至没有错”吗?这是否可能与请求或我需要像 Selenium 这样的东西?我是否不正确地使用 POST?

旁注:如果上下文有帮助,对于位置球员,我这样做了(对投手也会做同样的事情):

base_url = 'https://www.fangraphs.com/projections.aspx?pos=&stats=bat&type=zips&team=0&lg=all&players=0'
positions = ['c', '1b', '2b', '3b', 'ss', 'lf', 'cf', 'rf']

def generate_positional_urls(urlRoot=base_url, pos=positions):
    index = base_url.index('pos=') + 4 position after
    urlList = []
    numPositions = len(positions)
    for i in range(numPositions):
        position = pos[i]
        tempURL = urlRoot[:index] + position + urlRoot[index:]
        urlList.append(tempURL)

    return urlList

这会影响到这一点:

def generate_df(url):
page = urlopen(url)
soup = BeautifulSoup(page, 'html.parser')

masterTable = soup.findAll('table', {"class": "rgMasterTable"}, {"id": "ProjectionBoard1_dg1_ct100"})
table_rows = masterTable[0].find_all('tr')

data = []

for tr in table_rows:
    td = tr.find_all('td')
    row = [tr.text for tr in td]
    data.append(row)
    
headers = masterTable[0].find_all("thead")
colElements = headers[0].find_all("tr")

soupColResults = colElements[1].findChildren("a")
colStrings = [element.text for element in soupColResults]

df = pd.DataFrame(data)
df.columns = colStrings

df.drop(columns=[''], inplace=True)
df = df.iloc[3:, :]

return df

然后循环和连接数据帧就很容易了,但是对于这个HTTP问题,我真的不知道我在做什么。

注意:我这样做是为了尝试学习和练习 Python。我可以通过使用我的网络浏览器单击超链接以将数据导出到 csv 来轻松避免这种情况,因此不要花费太多精力来找出不必要的乏味。

标签: pythonhtml

解决方案


首先,您正在尝试将html表格刮入pandas数据框中。这可以在不BeautifulSoup使用pd.read_html. 其次,始终跟踪Chrome 或 Firefox的网络选项卡中发生的事情。当您单击以更改结果数量时,您可以看到加载的内容。您可以将请求复制curl然后将其转换为 python request,例如使用此工具。您现在可以加载数据并将它们pandas直接传递给。请注意,为了便于阅读,我必须删除__VIEWSTATE__EVENTVALIDATION,但您可以从页面源代码或从上述curl命令中获取它们。

import requests

params = (
    ('pos', 'all'),
    ('stats', 'pit'),
    ('type', 'zips'),
    ('team', '0'),
    ('lg', 'al'),
    ('players', '0'),
)

data = {
  'RadScriptManager1_TSM': '',
  '__EVENTTARGET': 'ProjectionBoard1$dg1',
  '__EVENTARGUMENT': 'FireCommand:ProjectionBoard1$dg1$ctl00;PageSize;1000',
  '__VIEWSTATE': '', #text removed for readability
  '__VIEWSTATEGENERATOR': 'C239D6F0',
  '__SCROLLPOSITIONX': '0',
  '__SCROLLPOSITIONY': '0',
  '__EVENTVALIDATION': '', #text removed for readability
  'ProjectionBoard1_tsStats_ClientState': '{"selectedIndexes":["1"],"logEntries":[],"scrollState":{}}',
  'ProjectionBoard1_tsPosition_ClientState': '{"selectedIndexes":["0"],"logEntries":[],"scrollState":{}}',
  'ProjectionBoard1$rcbTeam': 'All Teams',
  'ProjectionBoard1_rcbTeam_ClientState': '',
  'ProjectionBoard1$rcbLeague': 'AL',
  'ProjectionBoard1_rcbLeague_ClientState': '',
  'ProjectionBoard1_tsProj_ClientState': '{"selectedIndexes":["0"],"logEntries":[],"scrollState":{}}',
  'ProjectionBoard1_tsUpdate_ClientState': '{"selectedIndexes":[],"logEntries":[],"scrollState":{}}',
  'ProjectionBoard1$dg1$ctl00$ctl02$ctl00$PageSizeComboBox': '1000',
  'ProjectionBoard1_dg1_ctl00_ctl02_ctl00_PageSizeComboBox_ClientState': '{"logEntries":[],"value":"1000","text":"1000","enabled":true,"checkedIndices":[],"checkedItemsTextOverflows":false}',
  'ProjectionBoard1$dg1$ctl00$ctl03$ctl01$PageSizeComboBox': '30',
  'ProjectionBoard1_dg1_ctl00_ctl03_ctl01_PageSizeComboBox_ClientState': '',
  'ProjectionBoard1_dg1_ClientState': ''
}

response = requests.post('https://www.fangraphs.com/projections.aspx', params=params, data=data)
df = pd.read_html(response.text)

read_html以数据框的形式返回所有表的列表,您想要的表可以作为df[8]. 输出df[8].head()

姓名 未命名:1 团队 W 大号 时代 GS G 知识产权 H 急诊室 人力资源 所以 BB 鞭子 K/9 BB/9 FIP 战争 ADP
0 卢卡斯·乔利托 CHW 16 7 3 30 30 180 129 60 21 248 56 1.03 12.4 2.8 2.98 5.8 18.3
1 格里特·科尔 纽约年 16 8 3.21 32 32 193.3 155 69 27 260 49 1.06 12.1 2.28 3.18 5.7 7.4
2 肖恩比伯 CLE 14 9 3.51 32 32 197.7 177 77 26 230 36 1.08 10.47 1.64 3.23 5.4 10
3 何塞·贝里奥斯 最小 13 10 3.89 31 31 180.3 167 78 24 187 56 1.24 9.33 2.79 3.98 3.5 82.9
4 克里斯·塞尔 操作系统 9 5 3.35 20 20 121 103 45 14 147 28 1.08 10.93 2.08 3.23 3.4 297

推荐阅读