【问题标题】:mechanize submit form character encoding problem机械化提交表单字符编码问题
【发布时间】:2011-09-30 09:54:29
【问题描述】:

我正在尝试抓取http://www.nscb.gov.ph/ggi/database.asp,特别是您从选择市/省获得的所有表格。我正在使用带有 lxml.html 和机械化的 python。到目前为止,我的刮刀工作正常,但是在提交市政当局[19]“Peñarrubia,Abra”时,我得到了HTTP Error 500: Internal Server Error。我怀疑这是由于字符编码。我的猜测是 ene 字符(上面带有波浪号的 n)会导致这个问题。我该如何解决这个问题?

我的脚本这部分的一个工作示例如下所示。由于我刚开始使用 python(并且经常使用我在 SO 上找到的 sn-ps),因此非常感谢任何进一步的 cmets。

from BeautifulSoup import BeautifulSoup
import mechanize
import lxml.html
import csv



class PrettifyHandler(mechanize.BaseHandler):
    def http_response(self, request, response):
        if not hasattr(response, "seek"):
            response = mechanize.response_seek_wrapper(response)
        # only use BeautifulSoup if response is html
        if response.info().dict.has_key('content-type') and ('html' in response.info().dict['content-type']):
            soup = BeautifulSoup(response.get_data())
            response.set_data(soup.prettify())
        return response

site = "http://www.nscb.gov.ph/ggi/database.asp"

output_mun = csv.writer(open(r'output-municipalities.csv','wb'))
output_prov = csv.writer(open(r'output-provinces.csv','wb'))

br = mechanize.Browser()
br.add_handler(PrettifyHandler())


# gets municipality stats
response = br.open(site)
br.select_form(name="form2")
muns = br.find_control("strMunicipality2", type="select").items
# municipality #19 is not working, those before do
for pos, item in enumerate(muns[19:]): 
    br.select_form(name="form2")
    br["strMunicipality2"] = [item.name]
    print pos, item.name 
    response = br.submit(id="button2", type="submit")
    html = response.read()
    root = lxml.html.fromstring(html)
    table = root.xpath('//table')[1]
    data = [
               [td.text_content().strip() for td in row.findall("td")] 
               for row in table.findall("tr")
           ]
    print data, "\n"
    for row in data[2:]:
        if row: 
            row.append(item.name)
            output_mun.writerow([s.encode('utf8') if type(s) is unicode else s for s in row])
    response = br.open(site) #go back button not working

# provinces follow here

非常感谢!

编辑:具体来说,错误发生在这一行

response = br.submit(id="button2", type="submit")

【问题讨论】:

  • 有趣的问题。我想办法解决它,但一无所获。在我看来,问题不在于您自己的代码,因为如果您更改item.name 的编码,mechanize 会抛出insufficient items with name 'whatever_here'。所以似乎通过使用item.name 表单选择正确发生,但是在“发送”时错误的数据被传递到服务器。我注意到您正在抓取的页面位于iso-8859-1,而不是utf-8,但是将编码更改为拉丁文也不起作用。很想知道是否有人会解决!
  • 我也尝试通过设置br._factory.encoding = "iso-8859-1"br._factory._forms_factory.encoding = "iso-8859-1"br._factory._links_factory._encoding = "iso-8859-1"来改变mechanizeas suggested here的编码,但是没有成功。
  • 弄清楚如何设置“Content-Type”响应头并将值设置为“text/html; charset=iso-8859-1”。
  • 我已经尝试了机械化文档中的解决方案,但无济于事。奇怪的是,提交表单时出现错误。
  • 嗅探带有这些值的浏览器请求和带有您的代码的请求(您可以使用wireshark)也许您应该以服务器在页面的Content-Type标头中告诉您的相同编码发送表单数据具有表单(服务器发送 Content-Type: text/html without charset),因此浏览器通常会从页面(latin1)中选择内容,浏览器请求是否有效

标签: python encoding mechanize scraper


【解决方案1】:

快速而肮脏的破解:

def _pairs(self):
    return [(k, v.decode('utf-8').encode('latin-1')) for (i, k, v, c_i) in self._pairs_and_controls()]

from mechanize import HTMLForm
HTMLForm._pairs = _pairs

或者一些侵入性较小的东西(我认为没有其他解决方案,因为类 Item 保护“名称”字段)

item.__dict__['name'] = item.name.decode('utf-8').encode('latin-1')

之前

br["strMunicipality2"] = [item.name]

【讨论】:

  • 两种解决方案都有效。 (唯一的区别是第二个字符在终端中显示为灰色空白)。但是您能解释一下这些解决方案的具体作用,以及它们为什么起作用吗?我对这一切都很陌生,我试过br["strMunicipality2"] = [ item.name.decode('utf-8').encode('latin-1') ],但没有用。 item.__dict__['name'] = item.name.decode('utf-8').encode('latin-1') 到底有什么不同?
  • 实际上,第二种解决方案不起作用。它仅在我使用特定自治市for pos, item in enumerate(muns[19:]): 启动循环时才有效,如果我删除[19:] 并且其他市政当局在相关市政当局之前提交,我仍然会得到insufficient items with name 'Pe\xf1arrubia+Abra'。第一个解决方案似乎确实有效。
  • mechanize 从页面中选择选项,我不知道为什么,但它会转换 utf8 中的某处项目名称,然后使用 br['xxx'] = ['abc'] 选择一个项目查找表单值,因此您应该传递其项目列表中的名称。当它创建帖子时,所有密钥对都被 urlencoded,按原样机械化 urlquotes 值(但它已转换为 utf8)。您正在查询的页面是 latin1 中的期望值
  • 要在终端中查看正确的字符,您应该使用使用 latin1 (iso-8859-1) 的语言环境(检查 LANG 环境变量),并且终端应该显示为 latin1(很可能您的终端有utf8)
  • 第二个解决方案可能不起作用,或者更可能是第一个解决方案,因为它利用了机械化的内部结构,并且在不同的版本中可能会有所不同,我需要更深入地查看是否可以修复无需劫持机械化代码
【解决方案2】:

好的,找到了。这是一个漂亮的汤,可以转换为 unicode,prettify 默认返回 utf-8。 你应该使用:

response.set_data(soup.prettify(encoding='latin-1'))

【讨论】:

  • 我完全忘记了美味的汤。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-13
  • 1970-01-01
  • 1970-01-01
  • 2013-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多