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-Agent、Accept-Encoding、Accept和Connection,hook事件句柄钩子默认为一个仅有response键的字典, 值默认为listverify为True默认开启ssl验证cookie为一个RequestsCookieJar对象, 实则是cookielib模块下CookieJar的dict接口,将cookie参数传入的更新到cookiejar中adapters为有序字典, 默认会注入http、https前缀的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')
最后再将Request再wrapper一层为PreparedRequest, 调用其prepare方法, 这次会做些更为细腻的操作,将get方法转为大写再进行ascii编码, 整合会话与请求的参数等, 内部会调用多个处理函数
prepare_method兼容py2.xprepare_url格式化url, 将http协议的url解析,正则匹配各个字段,矫正、重组prepare_headers首部格式化,键值放入非大小敏感字典中, 核对键值对的有效性prepare_cookie将cookie注入首部, 先是构造MockRequest, 然后在CookieJar的add_cookie_header方法里解析cookie来完成mockprepare_body请求数据检查,文本还是生成器、迭代器、句柄, 流是否分块, 句柄指针结束标识,为表单数据添加相应首部字段Content-Typeprepare_authsession的auth属性默认是None的, 这里会解析url中的auth信息, 若url中包含auth, 以此auth构造HTTPBasicAuth实例, 并更新到session中, 同时更新body长度prepare_hooks注册钩子函数前须完成auth的相关检查,因为auth可以添加钩子,不能漏
session默认是无代理的,这样一来, send方法就仅发送一个PreparedRequest与允许重定向字段,
send方法实际是调用的adapter的send方法, 然后就是httplib、urllib3等封装的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()今天尝试用python的socket抓搜狐首页, 将问题又重新定位了下,一开始用 python的plain 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')
python的str类构造函数有提供编码错误的应对机制
具体细节得去看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再分配放大的使用不当造成, 实在太大可以采用指针数组, 分批次存储响应报文