无法使用 post 请求进入下一页

Posted

技术标签:

【中文标题】无法使用 post 请求进入下一页【英文标题】:Can't go on to the next page using post request 【发布时间】:2018-12-08 13:25:50 【问题描述】:

我用 python 编写了一个脚本来获取不同链接,这些链接指向网页上的不同文章。运行我的脚本后,我可以完美地得到它们。但是,我面临的问题是文章链接遍历多个页面,因为它们的数量很大以适合单个页面。如果我单击下一页按钮,我可以在开发人员工具中看到附加信息,这些信息实际上通过发布请求产生 ajax 调用。由于该下一页按钮没有附加链接,因此我找不到任何方法可以进入下一页并从那里解析链接。我试过用post requestformdata,但它似乎不起作用。我哪里错了?

Link to the landing page containing articles

这是我在单击下一页按钮时使用 chrome 开发工具获得的信息:

GENERAL
=======================================================
Request URL: https://www.ncbi.nlm.nih.gov/pubmed/
Request Method: POST
Status Code: 200 OK
Remote Address: 130.14.29.110:443
Referrer Policy: origin-when-cross-origin

RESPONSE HEADERS
=======================================================
Cache-Control: private
Connection: Keep-Alive
Content-Encoding: gzip
Content-Security-Policy: upgrade-insecure-requests
Content-Type: text/html; charset=UTF-8
Date: Fri, 29 Jun 2018 10:27:42 GMT
Keep-Alive: timeout=1, max=9
NCBI-PHID: 396E3400B36089610000000000C6005E.m_12.03.m_8
NCBI-SID: CE8C479DB3510951_0083SID
Referrer-Policy: origin-when-cross-origin
Server: Apache
Set-Cookie: ncbi_sid=CE8C479DB3510951_0083SID; domain=.nih.gov; path=/; expires=Sat, 29 Jun 2019 10:27:42 GMT
Set-Cookie: WebEnv=1Jqk9ZOlyZSMGjHikFxNDsJ_ObuK0OxHkidgMrx8vWy2g9zqu8wopb8_D9qXGsLJQ9mdylAaDMA_T-tvHJ40Sq_FODOo33__T-tAH%40CE8C479DB3510951_0083SID; domain=.nlm.nih.gov; path=/; expires=Fri, 29 Jun 2018 18:27:42 GMT
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-UA-Compatible: IE=Edge
X-XSS-Protection: 1; mode=block

REQUEST HEADERS
========================================================
Accept: text/html, */*; q=0.01
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 395
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Cookie: ncbi_sid=CE8C479DB3510951_0083SID; _ga=GA1.2.1222765292.1530204312; _gid=GA1.2.739858891.1530204312; _gat=1; WebEnv=18Kcapkr72VVldfGaODQIbB2bzuU50uUwU7wrUi-x-bNDgwH73vW0M9dVXA_JOyukBSscTE8Qmd1BmLAi2nDUz7DRBZpKj1wuA_QB%40CE8C479DB3510951_0083SID; starnext=MYGwlsDWB2CmAeAXAXAbgA4CdYDcDOsAhpsABZoCu0IA9oQCZxLJA===
Host: www.ncbi.nlm.nih.gov
NCBI-PHID: 396E3400B36089610000000000C6005E.m_12.03
Origin: https://www.ncbi.nlm.nih.gov
Referer: https://www.ncbi.nlm.nih.gov/pubmed
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
X-Requested-With: XMLHttpRequest

FORM DATA
========================================================
p$l: AjaxServer
portlets: id=relevancesortad:sort=;id=timelinead:blobid=NCID_1_120519284_130.14.22.215_9001_1530267709_1070655576_0MetA0_S_MegaStore_F_1:yr=:term=%222015%22%5BDate%20-%20Publication%5D%20%3A%20%223000%22%5BDate%20-%20Publication%5D;id=reldata:db=pubmed:querykey=1;id=searchdetails;id=recentactivity
load: yes

到目前为止,这是我的脚本(如果未注释,get 请求可以正常工作,但对于第一页):

import requests
from urllib.parse import urljoin
from bs4 import BeautifulSoup

geturl = "https://www.ncbi.nlm.nih.gov/pubmed/?term=%222015%22%5BDate+-+Publication%5D+%3A+%223000%22%5BDate+-+Publication%5D"
posturl = "https://www.ncbi.nlm.nih.gov/pubmed/"

# res = requests.get(geturl,headers="User-Agent":"Mozilla/5.0")
# soup = BeautifulSoup(res.text,"lxml")
# for items in soup.select("div.rslt p.title a"):
#     print(items.get("href"))

FormData=
    'p$l': 'AjaxServer',
    'portlets': 'id=relevancesortad:sort=;id=timelinead:blobid=NCID_1_120519284_130.14.22.215_9001_1530267709_1070655576_0MetA0_S_MegaStore_F_1:yr=:term=%222015%22%5BDate%20-%20Publication%5D%20%3A%20%223000%22%5BDate%20-%20Publication%5D;id=reldata:db=pubmed:querykey=1;id=searchdetails;id=recentactivity',
    'load': 'yes'
    

req = requests.post(posturl,data=FormData,headers="User-Agent":"Mozilla/5.0")
soup = BeautifulSoup(req.text,"lxml")
for items in soup.select("div.rslt p.title a"):
    print(items.get("href"))

顺便说一句,当我点击下一页链接时,浏览器中的 url 变为“https://www.ncbi.nlm.nih.gov/pubmed”。

我不想寻求与任何浏览器模拟器相关的任何解决方案。提前致谢。

【问题讨论】:

你会认为 selenium 是一个浏览器模拟器吗?因为那是你需要的模块。 您似乎在尝试模拟错误的请求。第一个发给pubmed(不是/pubmed/)的帖子是你需要的 您可能对这个 Github 存储库感兴趣,它使用 NCBI 的 E-utilities 端点,我相信它会返回许多相同的信息:github.com/jordibc/entrez 【参考方案1】:

内容是高度动态的,因此最好使用selenium 或类似的客户端,但我意识到这不切实际,因为结果的数量如此之大。因此,我们必须分析浏览器提交的 HTTP 请求,并用requests 模拟它们。

下一页的内容通过POST请求加载到/pubmed,post数据是EntrezForm表单的输入字段。表单提交由js控制(点击“下一页”按钮时触发),使用.submit()方法执行。

经过一番检查,我发现了一些有趣的领域:

EntrezSystem2.PEntrez.PubMed.Pubmed_ResultsPanel.Pubmed_Pager.CurrPageEntrezSystem2.PEntrez.PubMed.Pubmed_ResultsPanel.Pubmed_Pager.cPage 表示当前页面和下一页。

EntrezSystem2.PEntrez.DbConnector.Cmd 似乎执行了数据库查询。如果我们不提交此字段,结果将不会改变。

EntrezSystem2.PEntrez.PubMed.Pubmed_ResultsPanel.Pubmed_DisplayBar.PageSizeEntrezSystem2.PEntrez.PubMed.Pubmed_ResultsPanel.Pubmed_DisplayBar.PrevPageSize 表示每页的结果数。

有了这些信息,我可以使用下面的脚本获得多个页面。

import requests
from urllib.parse import urljoin
from bs4 import BeautifulSoup

geturl = "https://www.ncbi.nlm.nih.gov/pubmed/?term=%222015%22%5BDate+-+Publication%5D+%3A+%223000%22%5BDate+-+Publication%5D"
posturl = "https://www.ncbi.nlm.nih.gov/pubmed/"

s = requests.session()
s.headers["User-Agent"] = "Mozilla/5.0"

soup = BeautifulSoup(s.get(geturl).text,"lxml")
inputs = i['name']: i.get('value', '') for i in soup.select('form#EntrezForm input[name]')

results = int(inputs['EntrezSystem2.PEntrez.PubMed.Pubmed_ResultsPanel.Pubmed_ResultsController.ResultCount'])
items_per_page = 100
pages = results // items_per_page + int(bool(results % items_per_page))

inputs['EntrezSystem2.PEntrez.PubMed.Pubmed_ResultsPanel.Pubmed_DisplayBar.PageSize'] = items_per_page
inputs['EntrezSystem2.PEntrez.PubMed.Pubmed_ResultsPanel.Pubmed_DisplayBar.PrevPageSize'] = items_per_page
inputs['EntrezSystem2.PEntrez.DbConnector.Cmd'] = 'PageChanged'

links = []

for page in range(pages):
    inputs['EntrezSystem2.PEntrez.PubMed.Pubmed_ResultsPanel.Pubmed_Pager.CurrPage'] = page + 1
    inputs['EntrezSystem2.PEntrez.PubMed.Pubmed_ResultsPanel.Pubmed_Pager.cPage'] = page

    res = s.post(posturl, inputs)
    soup = BeautifulSoup(res.text, "lxml")

    items = [i['href'] for i in soup.select("div.rslt p.title a[href]")]
    links += items

    for i in items:
        print(i)

我要求每页 100 个项目,因为更高的数字似乎会“破坏”服务器,但您应该能够通过一些错误检查来调整该数字。

最后,链接按降序显示(/29960282/29960281,...),所以我认为我们可以在不执行任何 POST 请求的情况下计算链接:

geturl = "https://www.ncbi.nlm.nih.gov/pubmed/?term=%222015%22%5BDate+-+Publication%5D+%3A+%223000%22%5BDate+-+Publication%5D"
posturl = "https://www.ncbi.nlm.nih.gov/pubmed/"

s = requests.session()
s.headers["User-Agent"] = "Mozilla/5.0"
soup = BeautifulSoup(s.get(geturl).text,"lxml")

results = int(soup.select_one('[name$=ResultCount]')['value'])
first_link = int(soup.select_one("div.rslt p.title a[href]")['href'].split('/')[-1])
last_link = first_link - results

links = [posturl + str(i) for i in range(first_link, last_link, -1)]

但不幸的是,结果并不准确。

【讨论】:

您是说内容是动态 AJAX 处理的吗?动态是什么意思? @Cole 在#EntrezForm 提交时加载下一页的内容,通过点击Next> 链接触发(该链接上有一个事件监听器)。似乎不是 XHR 请求,但我可能是错的。如果你想研究代码,可以在这里找到:/portal/js/portal.js @Topto 是的,我同意。如果链接顺序一致,我的第二次尝试将是理想的,但这里有几个数字之间的“差距”。第一种方法产生了正确的结果,但所需的请求数量很大——这个查询需要 40,590 个!【参考方案2】:

不要将此问题视为 XY 问题,因为如果解决了,应该会提出一个非常有趣的解决方案但是我已经找到了一个更有效的解决这个特定问题的解决方案:使用@ 987654321@ 和方便的 opensource, unofficial Entrez repo。

使用我的PATH 中来自Entrez 存储库的entrez.py 脚本,我创建了这个脚本,它可以按照您的需要打印出链接:

from entrez import on_search
import re

db = 'pubmed'
term = '"2015"[Date - Publication] : "3000"[Date - Publication]'
link_base = f'https://www.ncbi.nlm.nih.gov/db/'

def links_generator(db, term):
    for line in on_search(db=db, term=term, tool='link'):
        match = re.search(r'<Id>([0-9]+)</Id>', line)
        if match: yield (link_base + match.group(1))

for link in links_generator(db, term):
    print(link)

输出:

https://www.ncbi.nlm.nih.gov/pubmed/29980165
https://www.ncbi.nlm.nih.gov/pubmed/29980164
https://www.ncbi.nlm.nih.gov/pubmed/29980163
https://www.ncbi.nlm.nih.gov/pubmed/29980162
https://www.ncbi.nlm.nih.gov/pubmed/29980161
https://www.ncbi.nlm.nih.gov/pubmed/29980160
https://www.ncbi.nlm.nih.gov/pubmed/29980159
https://www.ncbi.nlm.nih.gov/pubmed/29980158
https://www.ncbi.nlm.nih.gov/pubmed/29980157
https://www.ncbi.nlm.nih.gov/pubmed/29980156
https://www.ncbi.nlm.nih.gov/pubmed/29980155
https://www.ncbi.nlm.nih.gov/pubmed/29980154
https://www.ncbi.nlm.nih.gov/pubmed/29980153
https://www.ncbi.nlm.nih.gov/pubmed/29980152
https://www.ncbi.nlm.nih.gov/pubmed/29980151
https://www.ncbi.nlm.nih.gov/pubmed/29980150
https://www.ncbi.nlm.nih.gov/pubmed/29980149
https://www.ncbi.nlm.nih.gov/pubmed/29980148
...

如果与frontend page 相比,它们的顺序相同。 :-)

【讨论】:

以上是关于无法使用 post 请求进入下一页的主要内容,如果未能解决你的问题,请参考以下文章

如何解决我的提交按钮问题,我无法移动到下一页[重复]

Scrapy下一页按钮和上一页按钮在同一类,无法到达下一页

如何实现在分页的时候,点击下一页记录上一页选中的数据?

无法让我的脚本仅从顽固网站的下一页获取链接

无法导航到下一页 iPhone

php 表单 - 2 个按钮(一个用于销毁会话并重新加载页面,一个用于进入下一页)