【问题标题】:How to make use of POST request in the right way?如何以正确的方式使用 POST 请求?
【发布时间】:2017-12-09 05:59:28
【问题描述】:

我编写了一些代码来从网页中获取数据。该网站有下拉选项来选择更喜欢的项目。所以,首先我提出了一个GET 请求来形成网址,然后是一个POST 请求。我已经设法仅从它的第一页解析数据,但填充的结果显示在多个页面中。当我更改表单数据参数中的页码时,它对结果没有任何影响。我仍然从第一页得到结果。我怎样才能得到它们?顺便说一句,我没有根据自己的喜好从下拉选项中选择任何东西;相反,我开始搜索默认首选项是如何设置的。

链接到该网站:URL

这是我迄今为止尝试过的:

import requests
from bs4 import BeautifulSoup

payload={

    's':'opportunity',
    'mode':'list',
    'tab':'list',
    'pageID':3
}

r = requests.get("replace_with_above_url",params=payload,headers={'User-Agent':'Mozilla/5.0'})

payload={

    'dnf_class_values[procurement_notice][_posted_date]':'90',
    'dnf_class_values[procurement_notice][set_aside][]':'',
    'dnf_class_values[procurement_notice][zipstate]':'',
    'dnf_class_values[procurement_notice][procurement_type][]':'',
    'dnf_class_values[procurement_notice][keywords]':'',
    'autocomplete_input_dnf_class_values[procurement_notice][agency]':'',
    'dnf_class_values[procurement_notice][agency]':'',
    'so_form_prefix':'dnf_',
    'dnf_opt_action':'search',
    'dnf_opt_template':'vendor_procurement_notice_filter',
    'dnf_opt_mode':'update',
    'dnf_opt_finalize':'0',
    'dnf_opt_target':'',
    'dnf_opt_validate':'1',
    'dnf_class_values[procurement_notice][dnf_class_name]':'procurement_notice',
    'clear_filters_from_home':'1'   
}

res = requests.post(r.url,data=payload, headers={'User-Agent':'Mozilla/5.0'})
soup = BeautifulSoup(res.text,"lxml")
for item in soup.select(".solt"):
    print(item.text)

【问题讨论】:

    标签: python python-3.x web-scraping beautifulsoup http-post


    【解决方案1】:

    通过 Web 控制台检查网站显示,点击搜索按钮会发出一个包含查询字符串和表单数据参数的 POST 请求,同时单击下方的页面锚点会启动一个 GET 请求,其中仅包含查询字符串(和 pageID 参数集相应地)。

    我编辑了您的代码,添加了一个run 函数,该函数将页面ID 作为page 参数,如果page 等于1,则发出一个POST,否则发出一个GET:

    import requests
    from bs4 import BeautifulSoup
    
    payload={
    
        'dnf_class_values[procurement_notice][_posted_date]':'90',
        'dnf_class_values[procurement_notice][set_aside][]':'',
        'dnf_class_values[procurement_notice][zipstate]':'',
        'dnf_class_values[procurement_notice][procurement_type][]':'',
        'dnf_class_values[procurement_notice][keywords]':'',
        'autocomplete_input_dnf_class_values[procurement_notice][agency]':'',
        'dnf_class_values[procurement_notice][agency]':'',
        'so_form_prefix':'dnf_',
        'dnf_opt_action':'search',
        'dnf_opt_template':'vendor_procurement_notice_filter',
        'dnf_opt_mode':'update',
        'dnf_opt_finalize':'0',
        'dnf_opt_target':'',
        'dnf_opt_validate':'1',
        'dnf_class_values[procurement_notice][dnf_class_name]':'procurement_notice',
        'clear_filters_from_home':'1',
    }
    def run(page):
        url = "the given url"
        query = {
            's': 'opportunity',
            'mode': 'list',
            'tab': 'list',
            'pageID': page
        }
        if(page==1):
            r = requests.get(url, params=query, headers={'User-Agent': 'Mozilla/5.0'})
            res = requests.post(r.url,data=payload, headers={'User-Agent':'Mozilla/5.0'})
        else:
            res = requests.get(url, params=query, headers={'User-Agent': 'Mozilla/5.0'})
        soup = BeautifulSoup(res.text,"lxml")
        for item in soup.select(".solt"):
            print(item.text)
    
    for page in range(10):
        run(page + 1)
    

    此代码产生 200 行,即 10 页,每页 20 个结果。

    【讨论】:

      【解决方案2】:

      服务器使用会话 cookie“记住”您的搜索。您的代码会丢弃服务器返回的所有 cookie,因此每次发出新请求时都会重置内存。

      使用session object 记录传入的 cookie,并在后续请求中再次将它们发送出去:

      with requests.Session() as sess:
          sess.headers['User-Agent'] = 'Mozilla/5.0'
          r = sess.get("replace_with_above_url", params=payload)
      
          # ...
      
          res = sess.post(r.url, data=payload)
      

      然后您可以提交GET 请求以获取具有从1 开始的数字页面ID 的/index?s=opportunity&mode=list&tab=list&pageID= URL,直到遇到空的结果集:

      page_id = 0
      page_url = 'https://www.fbo.gov/index?s=opportunity&mode=list&tab=list&pageID={}'
      while True:
          page_id += 1 
          page = sess.get(page_url.format(page_id))
          soup = BeautifulSoup(page.text, "lxml")
          rows = soup.select('.solr-lst table tr')
          if len(rows) <= 1:
              break
          for row in rows[1:]:
              print([c.get_text(strip=True) for c in rows[1].select('td')])
      

      【讨论】:

      • 感谢 Martijn Pieters 先生的回答。你很亲密。我得到了很多重复。我不知道它是否是因为我的脚本而生成的。但是,我在这里粘贴了一个链接,您可以在其中看到我尝试组织您建议的部分以进行尝试。只要给我任何建议,告诉我我是否相应地做了,或者任何事情都应该在with 块之外。最后,我在脚本中使用的这个if not rows:break 部分是否可行。谢谢。这是链接:dropbox.com/home?preview=For+sir+Martijin.txt
      • @Topto:恐怕该链接实际上并没有通向任何地方。
      • @Topto:我调整了最后一页检测;一个空页面至少有 1 行(带标题)。
      • 对不起先生。现在检查一下。我希望它现在可以工作:dropbox.com/s/rq8rhmyz4nwvfep/For%20sir%20Martijin.txt?dl=0
      • @Topto: res 只是来自 HTTP POST 请求的响应对象。使用它来验证请求是否成功,或者只是不分配它而不使用它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-11-27
      • 1970-01-01
      • 2015-12-22
      • 2021-01-25
      • 1970-01-01
      • 2022-08-08
      • 2014-11-09
      相关资源
      最近更新 更多