python - 使用可自定义的下拉列表对表格进行 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 来轻松避免这种情况,因此不要花费太多精力来找出不必要的乏味。
解决方案
首先,您正在尝试将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 |