Python3.x、urllib、与HTTPCookieProcessor趣事

python基础

浏览数:86

2019-8-28

AD:资源代下载服务

从最简单的post请求说起

python与urllib目前被广泛使用于网络蜘蛛的开发。
为了能够像浏览器一样获取到目标网站的信息,除了构建合理的request参数以外,cookie的处理也是必不可少的环节之一,而urllib中的HTTPCookieProcessor能够十分智能化的帮助我们解决这一问题。

# 通过HTTPCookieProcessor()来获取一个能够处理cookie的
def get_opener(self, head):
        cookie = cookiejar.CookieJar()
        handler = urllib.request.HTTPCookieProcessor(cookie)
        opener = urllib.request.build_opener(handler)
        return opener

像通过上面的方式建立的openner,能够自动化处理请求过程中遇到的所有cookie数据,十分的方便。
接下来让我们尝试使用该openner发送一个post请求。

# 使用构建好的openner发送一个POST请求。
def go_login(self, openner):
        post_data = parse.urlencode(self.login_post_data).encode()
        # 通过openner发送请求
        response = openner.open(self.login_url, post_data)

请求发送成功,响应成功返回,大功告成。python真是一门短平快的语言!

伪装浏览器

仅仅成功发送一个请求是不够的,在实际项目中我们往往需要将蜘蛛伪装成
一个浏览器,从而达到欺骗目标服务器的目的。

# 请求头参数
    head = {
        'Accept': '*/*',
        'Host': 'www.python.com',
        'Connection': 'keep-alive',
        'Origin': 'www.python.com',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome'
                      '/51.0.2704.103 Safari/537.36',
        'Content-Type': 'application/json',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Referer': 'your_url',
        'Accept-Language': 'zh-CN,zh;q=0.8',
        'Accept-Encoding': 'gzip, deflate'
    }
    
     def get_opener(self, head):
        cookie = cookiejar.CookieJar()
        handler = urllib.request.HTTPCookieProcessor(cookie)
        opener = urllib.request.build_opener(handler)
        header = []
        # 字典转换为truple集合.
        for key, value in head.items():
            elem = (key, value)
            header.append(elem)
        opener.addheaders = header
        return opener

在python的世界里一切就是这么简单,使用addheaders方法很容易就解决了问题。
现在,我们的openner不仅可以搞定cookie,还能在每次处理中带上请求头参数,它已经是一个具备最基本功能的浏览器了,真是方便。

现在,让我们尝试使用新的Openner发送一个请求看看!

def search_region_info(self, openner, region_search_request_data):
        parsed_request_data = parse.urlencode(region_search_request_data).encode()
        response = openner.open(self.region_search_url, parsed_request_data)
        region_info_json = json.loads(response.read().decode())
        if len(region_info_json['Data']['Regions']) == 0:
            raise ValueError('%s地区无法查询到Region信息' % region_search_request_data['keyword'])
        return region_info_json

利用Charles抓包工具检查本次请求的请求头是否和我们预想的一致。


request1.png-111.8kB

Content-Type带来的严重问题

charles抓包出来的结果与我们预想的完全不同。现在麻烦大了,不仅伪装浏览器失败,Content-Type的差异会直接造成服务器解析参数失败等严重问题。

Content-Type:application/x-www-form-urlencoded

使用Content-Type:application/x-www-form-urlencoded提交的post请求一般是源生浏览器的表单数据提交的请求,他的请求参数会按照key1=val1&key2=val2 的方式进行编码。对于这样的数据,各服务器与控制层框架能够对请求参数进行很好的处理和映射。

Content-Type:application/json

而提交json格式的数据,也就是Content-Type:application/json时,在服务端是无法将复杂json格式(如1个json想要映射成2个Bean这样的需求)的数据,直接映射成2个javaBean对象以后获取的。比如在springMvc中,往往需要这样进行解析:

    @ResponseBody
   public Result updateCustomizedItineraryInfo(@RequestBody String requestBody) {
        Result result = new Result();
        JSONArray jsonArray = JSONArray.fromObject(requestBody);
        customizationService.updateCustomizedItineraryInfo(jsonArray);
        return result;
    }
    
    @ResponseBody
    public Result queryAirTicketList(@RequestBody SearchTicketParam searchTicketParam) {
        Result result = new Result();
        SearchTicketResult searchTicketResult = airTicketService.searchAirTickets(searchTicketParam);
        result.setData(searchTicketResult);
        return result;
    }

解决方案

笔者查阅了相关文档,但是没有找到为何cookie处理正常,而addheader没有正常工作的原因。(如果刚好你知道原因,请通过邮件也分享给我,谢谢!)
所以只好退而求其次使用下面这种方式发送Content-Type:application/json格式的post请求。

def search_hotel_info(self, checkindate, checkoutdate, region_id, openner):
        self.hotel_search_request_data['data']['CheckInDate'] = checkindate
        self.hotel_search_request_data['data']['CheckOutDate'] = checkoutdate
        self.hotel_search_request_data['data']['RegionID'] = region_id
        parsed_request_data = json.dumps(self.hotel_search_request_data).encode()
        req = request.Request(self.hotel_search_url, data=parsed_request_data, headers=self.head)
        request.install_opener(openner)
        response = urllib.request.urlopen(req)
        return response.read().decode()

request2.png-114.8kB

现在请求头终于按照预想数据发送了!
最后,如果你对本项目有兴趣欢迎通过我的github查看与下载源代码。

总结

  1. HTTPCookieProcessor能够自动化处理session。
  2. 通过HTTPCookieProcessor创建的opener在发送请求时,无法按照预想的request header发送请求。
  3. 我们通过install_opener与urlopen可以同时处理head与cookie。

作者:给你添麻烦了