python - 非常奇怪的网页抓取问题 : Post request not behaving as expected

标签 python web-scraping urllib2 mechanize

我正在尝试以编程方式向我们公司管理页面上的表单提交一些数据,而不是手动完成。

我已经编写了许多其他工具来抓取这个网站并处理数据。然而,出于某种原因,这个特别的人给了我一个 的麻烦。

用浏览器遍历:

下面是我试图抓取和发布数据的页面。请注意,这些页面通常显示在 js shadowboxes 中,但是,它在禁用 Javascript 的情况下运行良好,所以我假设 javascript 不是关于爬虫问题的问题。

(请注意,由于这是一个公司页面,我已经用垃圾标题替换了所有表单字段,因此,例如,客户编号完全是虚构的)

此外,由于它是用户名/密码墙后面的公司页面,我无法提供该网站进行测试,所以我尝试在这篇文章中注入(inject)尽可能多的细节!

主要入口点在这里:

enter image description here

从这个页,我点击"Add New form" ,它会在新标签中打开下一页(因为 javascript 被禁用)。

enter image description here

在这个页面上,我填写了一个小表单,点击提交,然后下一个页面显示成功消息。

enter image description here

应该很简单吧?

代码尝试 1:机械化

import mechanize
import base64
import cookielib


br = mechanize.Browser()

username = 'USERNAME'
password = 'PASSWORD'
br.addheaders.append(('Authorization', 
    'Basic %s' % base64.encodestring('%s:%s' % (username, password))))
br.addheaders = [('User-agent', 
    'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.22 (KHTML,'
    ' like Gecko) Chrome/25.0.1364.172 Safari/537.22')]

br.open('www.our_company_page.com/adm/add_forms.php')

links = [link for link in br.links()]

# Follow "Add a form" Link
response = br.follow_link(links[0]) 

br.select_form(nr=0)
br.form.set_all_readonly(False)
br.form['formNumber'] = "FROM_PYTHON"
br.form['RevisionNumber'] = ['20']
br.form['FormType'] = ['H(num)']

response = br.submit()

print response.read() #Shows the exact same page! >:(

因此,如您所见,我尝试复制在浏览器中执行的步骤。我加载初始 /adm/forms页面,点击第一个链接,即 Add a Form ,并填写表格,然后单击 submit按钮。但这就是它变得棘手的地方。机械化返回的响应与表单完全相同。没有错误消息,没有成功消息,当我手动检查我们的管理页面时,没有进行任何更改。

检查网络事件

沮丧的是,当我手动提交并在浏览器中提交表单时,我打开 Chrome 并查看网络选项卡。

提交表单后,这是网络事件:

enter image description here

对我来说似乎很直接。这是post ,然后是 get对于 css 文件,还有另一个 get对于 jquery 库。还有一个get对于某种图像,但我不知道那是为了什么。

检查 POST 请求的详细信息:

enter image description here

在对抓取问题进行了一些谷歌搜索之后,我看到一个建议,即服务器可能需要某个 header ,我应该简单地复制在 POST 请求中生成的所有内容,然后慢慢取走 header ,直到我弄清楚哪个是重要的一。所以我就这样做了,复制了“网络”选项卡中的每一点信息,并卡在了我的帖子请求中。

代码尝试 2:Urllib

我在用 Mechanize 找出所有标题时遇到了一些麻烦。 ,所以我切换到 urllib2。
import urllib
import urllib2
import base64 



url = 'www.our_company_page.com/adm/add_forms.php'
values = {
    'SID':'', #Hidden field
    'FormNumber':'FROM_PYTHON1030PM',
    'RevisionNumber':'5',
    'FormType':'H(num)',
    'fsubmit':'Save Page'
    }
username = 'USERNAME'
password = 'PASSWORD'

headers = { 
    'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Charset' : 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
    'Accept-Encoding' : 'gzip,deflate,sdch',
    'Accept-Language' : 'en-US,en;q=0.8',
    'User-Agent' :  'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 
    'Authorization': 'Basic %s' % base64.encodestring('%s:%s' % (username, password)),
    'Cache-Control' : 'max-age=0',
    'Connection' : 'keep-alive',
    'Content-Type' : 'application/x-www-form-urlencoded',
    'Cookie' : 'ID=201399',
    'Host' : 'our_company_page.com',
    'Origin' : 'http://our_company_page.com',
    'Referer' : 'http://our_company_page.com/adm/add_form.php',
    'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, ' 
            'like Gecko) Chrome/26.0.1410.43 Safari/537.31'
    }

data = urllib.urlencode(values)
req = urllib2.Request(url, data, headers)
response = urllib2.urlopen(req)
print response.read()

如您所见,我将 Chrome 网络选项卡中的 header 添加到 urllib2 中的 POST 请求中。 .

Mechainze 版本的另一个变化是我现在可以访问 add_form.php通过将相关 cookie 添加到我的请求中来直接访问页面。

然而,即使尽我所能复制,我仍然有完全相同的问题:响应与我开始时完全相同的页面 - 没有错误,没有成功消息,服务器上没有更改,只是返回到空白表单。

最后一步:绝望了,我安装了 WireShark

是时候进行一些流量嗅探了。我决心在这个神奇的帖子请求中看到 WTF 正在进行中!

我下载、安装并启动 Wireshark。我过滤 http ,然后首先在浏览器中手动提交表单,然后运行我的代码并尝试以编程方式提交表单。

这是网络流量:

浏览器:

enter image description here

Python:

enter image description here

除了标题的顺序略有不同(这很重要)之外,它们看起来完全一样!

所以这就是我所在的地方,完全不明白为什么是 post请求(据我所知)与浏览器发出的请求几乎相同,它不会对服务器进行任何更改。

有没有人遇到过这样的事情?我错过了一些明显的东西吗?这里发生了什么?

编辑

根据 Ric 的建议,我复制了 POST数据准确。我直接从 Chrome 中的网络源选项卡复制它。

修改后的代码如下
data = 'SegmentID=&Segment=FROMPYTHON&SegmentPosition=1&SegmentContains=Sections&fsubmit=Save+Page'
req = urllib2.Request(url, data, headers)
response = urllib2.urlopen(req)
print response.read()

我唯一改变的是Segment值来自 FROMBROWSERFROMPYTHON .

不幸的是,这仍然产生相同的结果。响应是同一个页面,我从这里开始。

更新

工作,但没有解决

我查看了 requests库,使用他们的 API 重复了我的努力,并且神奇地工作了! POST实际上通过了。问题仍然存在:为什么!?我再次使用wireshark拍摄了另一个快照,据我所知,它与浏览器发出的POST完全相同。

编码
def post(eventID, name, pos, containsID):

    segmentContains = ["Sections", "Products"]
    url = 'http://my_site.com/adm/add_page.php'
    cookies = dict(EventID=str(eventID))
    payload = { "SegmentID" : "",
                "FormNumber" : name,
                "RevisionNumber" : str(pos),
                "FormType" : containsID,
                "fsubmit" : "Save Page"
            }

    r = requests.post(
            url, 
            auth=(auth.username, auth.password),
            allow_redirects=True,
            cookies=cookies,
            data=payload) 

Wireshark 输出

要求

enter image description here

浏览器

enter image description here

因此,总结一下问题的当前状态。它有效,但我没有真正改变。我不知道为什么同时尝试 Mechanize 和 urllib2 都失败了。正在发生的事情允许requests POST要真正通过吗?

编辑——Wing Tang Wong 建议:

Wing Tand Wongs建议,我创建了一个 cookie 处理程序,并将其附加到 urllib.opener .所以没有更多的cookies被手动发送到标题中——事实上,我现在根本没有分配任何东西。

我首先连接到带有表单链接的 adm 页面,而不是立即连接到表单。
'http://my_web_page.com/adm/segments.php?&n=201399'

这给出了 ID cookie 给我的 urllib cookieJar .从这一点开始,我点击链接到包含表单的页面,然后尝试像往常一样提交给它。

完整代码:
url = 'http://my_web_page.com/adm/segments.php?&n=201399'
post_url = 'http://my_web_page.com/adm/add_page.php'
values = {
    'SegmentID':'',
    'Segment':'FROM_PYTHON1030PM',
    'SegmentPosition':'5',
    'SegmentContains':'Products',
    'fsubmit':'Save Page'
    }
username = auth.username
password = auth.password

headers = { 
    'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Charset' : 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
    'Accept-Encoding' : 'gzip,deflate,sdch',
    'Accept-Language' : 'en-US,en;q=0.8',
    'User-Agent' :  'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 
    'Authorization': 'Basic %s' % base64.encodestring('%s:%s' % (username, password)),
    'Cache-Control' : 'max-age=0',
    'Connection' : 'keep-alive',
    'Content-Type' : 'application/x-www-form-urlencoded',
    'Host' : 'mt_site.com',
    'Origin' : 'http://my_site.com',
    'Referer' : 'http://my_site.com/adm/add_page.php',
    'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.43 Safari/537.31'
    }

COOKIEFILE = 'cookies.lwp'
cj = cookielib.LWPCookieJar()

if os.path.isfile(COOKIEFILE):
    cj.load(COOKIEFILE)

opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)

data = urllib.urlencode(values)
req = urllib2.Request(url, headers=headers)
handle = urllib2.urlopen(req)

req = urllib2.Request(post_url, data, headers)
handle = urllib2.urlopen(req)

print handle.info()
print handle.read()
print
if cj:
    print 'These are the cookies we have received so far :'
    for index, cookie in enumerate(cj):
        print index, '  :  ', cookie
    cj.save(COOKIEFILE)

和以前一样。不会在服务器上进行任何更改。为了验证 cookie 确实存在,我在提交表单后将它们打印到控制台,输出如下:
These are the cookies we have received so far :
<Cookie EventID=201399 for my_site.com/adm>  

所以,cookie 就在那里,并且已经与请求一起发送了......所以仍然不确定发生了什么。

最佳答案

阅读并重新阅读您的帖子,其他人会回答几次。我的想法:

当您在 mechanize 和 urllib2 中实现时,看起来 cookie 被硬编码到 header 响应中。这很可能会导致表单将您踢出局。

当您切换到使用网络浏览器并使用 python 'requests' 库时,cookie 和 session 处理在幕后得到处理。

我相信,如果您更改代码以考虑 cookie 和 session 状态,即。在开始时假设一个自动 session ,该站点有一个空的 cookie 并且没有 session 数据,但是在 session 期间正确跟踪和管理它,它应该可以工作。

简单的复制和替换标题数据是行不通的,一个正确编码的站点应该会让你回到起点。

没有看到网站的后端代码,以上是我的观察。 Cookie 和 session 数据是罪魁祸首。

编辑:

找到这个链接:http://docs.python-requests.org/en/latest/

它描述了使用身份验证/等访问站点。身份验证的格式类似于您正在使用的请求实现。他们链接到一个 urllib2 实现的 git 源,它执行相同的操作,我注意到身份验证位与您执行身份验证位的方式不同:

https://gist.github.com/kennethreitz/973705

从页面:

password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_manager.add_password(None, gh_url, 'user', 'pass')

auth_manager = urllib2.HTTPBasicAuthHandler(password_manager)
opener = urllib2.build_opener(auth_manager)

我想知道您是否改变了为 urllib2 实现实现身份验证位的方式,它会起作用。

关于python - 非常奇怪的网页抓取问题 : Post request not behaving as expected,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15733078/

相关文章:

QPython 错误的 android.Android()

Python正则表达式引擎字符类中的转义括号

python - 为什么在 Selenium 中等待元素加载后元素仍未出现?

python - 打开 URL 时解码响应

带参数的循环中的 Python 线程模块?

python - 无属性 : 'audit' found while using smtplib to send emails

python - 从需要登录的页面中抓取数据

python 网络抓取 - len(containers) 总是返回 0

python - 在 urllib2.urlopen 中使用 mimetools.Message

python - 使用 Django 传递文件对象