您正在抓取的网站是由 asp.net WebForms 生成的。用户可以与之交互的所有内容都被视为“控件”,并且将具有动态生成的标识符,这些标识符取决于它们在容器中的顺序。这些标识符并不总是反映在元素的 id 属性中。当用户与页面交互时,会触发一个名为__doPostBack(eventTarget, eventArgs) 的函数,它会填写一些标准的隐藏表单字段,然后发布到服务器以获取更新的页面。 eventTarget 是被点击元素的 asp.net 标识符。
在这种情况下,您的容器是 ID 为 dgSearchResults 的表。
此站点上的分页导航是容器表的子项,因此导航将以相同的方式工作,使用__EVENTTARGET 进行回发。为了让你使用请求单步浏览网站,你需要知道你当前页面的索引,你需要找到到下一页的链接,你必须将你的帖子中的__EVENTTARGET设置为标识符那个链接。
首先,我们要使用几次正则表达式,所以让我们导入它:
import re
接下来,为了避免猛击网站,我们将在发布请求之间进行短暂的随机等待。这不是您问题的一部分,但这是一个重要的考虑因素。为此,我们将使用random.uniform() 和time.sleep():
from random import uniform
from time import sleep
您已经声明 p = 1 来跟踪页码。为了使代码更清晰一点,我会将p 更改为page_num,将其初始化为0,然后在页面循环的每次迭代中的发布请求之后递增它。此外,在每个发布请求之前,我们将等待 500 到 1000 毫秒之间的随机时间(您可以根据需要调整这些时间)。这种等待将避免猛击服务器,并且随机性有助于防止您的代码在服务器日志中看起来像一个机器人:
page_num = 0
while True:
wait_time = uniform(0.5, 1.0)
sleep(wait_time)
res = s.post(link,data=payload)
page_num += 1
soup = BeautifulSoup(res.text,"lxml")
if not soup.select_one("table#dgSearchResults tr:has(> td > a[href^='frmDocketImages.aspx'])"): break
包含分页链接的行有类grid-pager,所以让我们抓住它:
nav_row = soup.select_one("table#dgSearchResults tr.grid-pager")
if nav_row is None: break
第一页这一行的html是:
<tr class="grid-pager" align="right">
<td colspan="7">
<span>1</span>
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl01','')">2</a>
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl02','')">3</a>
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl03','')">4</a>
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl04','')">5</a>
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl05','')">6</a>
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl06','')">7</a>
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl07','')">8</a>
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl08','')">9</a>
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl09','')">10</a>
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl10','')">...</a>
</td>
因此,nav_row 是一个表格行元素,其中包含指向其他结果的链接的单个单元格。您当前的页面是<span> 标记的文本,指向下一个结果集的链接始终是下一个<a>。
要获取span,请使用正则表达式查找page_num。即使您要查找的 span 是该行中唯一的一个,但找到 page_num 可确保您获得所需的spans,以防后续添加任何其他 spans。
让我们获取该跨度和下一页的链接:
page_span = nav_row.find('span', string=re.compile(rf'\b{page}\b'))
next_page_link = page_span.find_next_sibling('a')
if next_page_link is None: break
如果没有next_page_link,我们就在列表的末尾(或者出现了问题 - 由您来检查),所以我们中断。 next_page_link的html是:
<a href="javascript:__doPostBack('dgSearchResults$ctl24$ctl01','')">2</a>
eventTarget - 这是我们在发布请求中需要 __EVENTTARGET 的值 - 是链接 href 中 __doPostBack() 中的第一个参数:
href = next_page_link.get('href')
接下来我们可以再次使用正则表达式来获取__EVENTTARGET 所需的标识符。 WebForms 的工作方式始终是doPostBack 之后的第一个引用项,并且始终是必需的。
match = re.search(r'doPostBack\([\'\"]([^\'\"]+)', href)
event_target = match[1]
请注意,代码假定总会有匹配项。您可能需要对其进行测试或添加自己的错误处理。
现在我们有了event_target,我们可以构建有效载荷:
payload = {i['name']:i.get('value','') for i in soup.select('input[name]')}
payload['__EVENTTARGET'] = event_target
payload.pop('cmdSearchNew', None)
print('-------------->',payload['__EVENTTARGET'])
就是这样。您的代码将在循环的下一次迭代中导航到下一页。将它们放在一起并使用您指定的输入运行它将浏览返回的所有 96 个页面:
import requests
from bs4 import BeautifulSoup
import re
from random import uniform
from time import sleep
link = 'https://registers.maryland.gov/RowNetWeb/Estates/frmEstateSearch2.aspx'
with requests.Session() as s:
s.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36'
r = s.get(link)
soup = BeautifulSoup(r.text,"lxml")
payload = {i['name']:i.get('value','') for i in soup.select('input[name]')}
payload.pop('cmdClear')
payload['cboCountyId'] = '3' #Baltimore (next to county)
payload['DateOfFilingFrom'] = '08-08-2020' #date from (under range)
payload['DateOfFilingTo'] = '01-01-2021' #date to (under range)
payload['cboPartyType'] = 'Decedent'
page_num = 0
while True:
wait_time = uniform(0.5, 1.0)
sleep(wait_time)
res = s.post(link,data=payload)
page_num += 1
soup = BeautifulSoup(res.text,"lxml")
if not soup.select_one("table#dgSearchResults tr:has(> td > a[href^='frmDocketImages.aspx'])"): break
for item in soup.select("table#dgSearchResults tr:has(> td > a[href^='frmDocketImages.aspx'])"):
data = [i.text for i in item.select("td")]
print(data)
nav_row = soup.select_one("table#dgSearchResults tr.grid-pager")
if nav_row is None: break
page_span = nav_row.find('span', string=re.compile(rf'\b{page_num}\b'))
next_page_link = page_span.find_next_sibling('a')
if next_page_link is None:
break
href = next_page_link.get('href')
match = re.search(r'doPostBack\([\'\"]([^\'\"]+)', href)
event_target = match[1]
payload = {i['name']:i.get('value','') for i in soup.select('input[name]')}
payload['__EVENTTARGET'] = event_target
payload.pop('cmdSearchNew', None)
print('-------------->',payload['__EVENTTARGET'])
print('DONE')