Posts
requests.get

requests.get

python网络编程练习过程中遇到的问题 心血来潮对当时python的request库的一番探究

c socket 写的跟python requests.get不一致 即使header相同,抓取的DOM也有区别, 一个1700行, 一个才200多 header字段不一样,光相应头跟部分meta,文本都出不来是什么鬼, user-agent换成python-requests, body至少有了点

requests 2.24.0

get调用的是Session对象的request方法

对一个会话实例,它有以下属性

  • header默认字段包括User-AgentAccept-EncodingAcceptConnection
  • hook 事件句柄钩子默认为一个仅有response键的字典, 值默认为list
  • verifyTrue默认开启ssl验证
  • cookie为一个RequestsCookieJar对象, 实则是cookielib模块下CookieJardict接口,将cookie参数传入的更新到cookiejar
  • adapters为有序字典, 默认会注入httphttps前缀的HTTPAdapter对象

再回到request方法, request方法构造一个Request实例,通常的get调用只指传递get方法名、url和默认的allow_redirects允许重定向字段到Request构造器,

接着session调用prepare_request方法会将request中的cookie参数与session中的cookie整合, 在安全环境下,会话与请求都没显示设置权限时,会尝试用netrc模块获取授权, 通常会返回空值requests.utils.get_netrc_auth('news.sohu.com')

最后再将Requestwrapper一层为PreparedRequest, 调用其prepare方法, 这次会做些更为细腻的操作,将get方法转为大写再进行ascii编码, 整合会话与请求的参数等, 内部会调用多个处理函数

  • prepare_method 兼容py2.x
  • prepare_url 格式化url, 将http协议的url解析,正则匹配各个字段,矫正、重组
  • prepare_headers 首部格式化,键值放入非大小敏感字典中, 核对键值对的有效性
  • prepare_cookiecookie注入首部, 先是构造MockRequest, 然后在CookieJaradd_cookie_header方法里解析cookie来完成mock
  • prepare_body 请求数据检查,文本还是生成器、迭代器、句柄, 流是否分块, 句柄指针结束标识,为表单数据添加相应首部字段Content-Type
  • prepare_auth sessionauth属性默认是None的, 这里会解析url中的auth信息, 若url中包含auth, 以此auth构造HTTPBasicAuth实例, 并更新到session中, 同时更新body长度
  • prepare_hooks 注册钩子函数前须完成auth的相关检查,因为auth可以添加钩子,不能漏

session默认是无代理的,这样一来, send方法就仅发送一个PreparedRequest与允许重定向字段, send方法实际是调用的adaptersend方法, 然后就是httpliburllib3等封装的socket抽象库实现的会话,收发消息

import socket, ssl
def cwl(url):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
    sock.connect((url, 443))
 
    sock = ssl.wrap_socket(sock)
 
    req = '''\
GET / HTTP/1.1\r\n\
Host: news.sohu.com\r\n\
User-Agent: python-requests/2.24.0\r\n\
Content-Language: zh-CN\r\n\
Connection: Close\r\n\
\r\n\
'''
    sock.sendall(req.encode('ascii'))
 
    f = open('d.html', 'w', encoding='utf8')
    raw = b''
    while 1:
        more = sock.recv(1<<12)
        # import ipdb; ipdb.set_trace()
        f.write(more.decode())
        if not more: break
        raw += more
  
    sock.close()
    f.close()

今天尝试用pythonsocket抓搜狐首页, 将问题又重新定位了下,一开始用 pythonplain http请求直接返回的400, 还以为会跟c一样443端口连接建立后自动协商, 老实套上ssl后发现一样是200多行的响应, 而且位置也差不多, ipdb调下发现常用的编码怎么都过不了, 感觉问题跟响应的解析可能也有关系,头大

sb了, 不用ssl加密连443端口连接都建立不起来


import socket, ssl
 
def cwl(host, port):
    req = f'''\
GET / HTTP/1.1\r\n\
Host: {host}\r\n\
User-Agent: wcw-demo\r\n\
Connection: Close\r\n\
\r\n\
'''
 
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, port))
    sock = ssl.wrap_socket(sock)
    
    sock.sendall(req.encode('utf8'))
 
    res = b''
    f = open('a.htm', 'w',  encoding='utf8')
    while 1:
        try:
            more = sock.recv(128)
            if not more: break
            # f.write(more.decode('utf8'))
            res += more 
        except Exception as e:
            f.close()
            print(e)
 
    f.write(str(res, 'utf-8', errors='replace'))
    f.close()

做事前还是得多想想, 误以为会逐行解析响应, 看到urllib3的连接池去了 最后总算定位到问题, 全部的二进制响应接受后, 在model.py中一处整体进行解析 content = str(self.content, encoding, errors='replace') pythonstr类构造函数有提供编码错误的应对机制 具体细节得去看python解释器的C库了


python的解码函数decode对指定编码外的错误编码字节, 通常会直接抛出UnicodeError异常, 指定errors参数采取不同的应对处理策略

  • replace, 使用U+FFFD来替换错误编码字节
  • ignore, 去掉该字符
  • blackslashreplace, 为字符添加转义符\

我现在的看法是, 响应经过层层传递, 以及unicode的多子节字符, 很可能在数据包传递的过程中发生错误, 一个bit位变化就会有字节受影响, 纠错校验通常也是基于计数, 仍可能漏掉异常,针对现在的问题,需要的是一个对异常utf8编码处理的函数,

还是有问题😂


唉, C爬下来都错误编码不影响解码, 响应序列长度不对主要在于, while循环recv的退出条件, 不像python中每次就是recv指定的响应长度, 小于其长度时就退出了, 还是得接受子序列长度为0才退出循环, 然后就是realloc调整动态内存长度的条件, 感觉明明很难刚好到指定的窗口, 先前的>=一直报错realloc error, 当然更简单的方法是直接在初次malloc时将bufsize设到最大


完结, python的不完整响应在于解码错误,解决方案是errors参数的设置, ignore或是replace, c中的问题在于之前while读操作的退出条件不准, buffer设置太小realloc再分配放大的使用不当造成, 实在太大可以采用指针数组, 分批次存储响应报文