Python使用Socket发送HTTP/HTTPS请求详解:解决400与301错误
本文详解如何使用Python原生Socket库发送HTTP及HTTPS请求。针对开发中常见的400 Bad Request和301重定向问题,分析了请求头格式、分段发送及HTTP与HTTPS协议差异(端口80与443)的影响,提供了基于Socket实现底层网络请求的完整代码示例与调试技巧。
Python Socket发送HTTP/HTTPS请求详解:解决400与301错误
前言
这是在进行计算机网络课程作业时遇到的一个具体问题,困扰了我整整一天,因此在此进行总结。
此前我曾使用requests库编写Python爬虫,但课程要求实现更底层的网络交互。恰好看到一篇使用Socket实现请求的文章,因此决定学习其实现方式。
起初认为这并不复杂,因为核心逻辑仅涉及建立TCP连接。
参考网站的示例代码如下:
def fetch(url):
sock = socket.socket() # 建立socket
sock.connect(('xkcd.com', 80)) # 远程连接
request = 'GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n'.format(url) # 构建请求
sock.send(request.encode('ascii')) # 向socket发送数据
response = b''
chunk = sock.recv(4096) # 从socket接收数据
while chunk:
response += chunk
chunk = sock.recv(4096)
# Page is now downloaded.
links = parse_links(response)
q.add(links)
我选择爬取的网站是链家,并参考了多个其他示例,同时使用Fiddler抓包工具获取完整的Headers。首先参考了这篇文章:https://segmentfault.com/a/1190000005126160,该文章指出:
Python通过Socket发送HTTP请求
我们以访问百度主页为例,演示如何使用Socket发送HTTP请求。
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('www.baidu.com',80))
s.send('''GET https://www.baidu.com/ HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
Accept-Language: zh-CN,zh;q=0.8
''')
buf=s.recv(1024)
while len(buf):
print buf
buf = s.recv(1024)
基于Socket进行HTTP编程的优势在于对请求参数的控制更加精细,但相应地增加了实现难度。上述发送的数据直接复制自Fiddler抓包结果。
基于以上分析,编写如下代码:
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('www.baidu.com',80))
s.send('''GET / HTTP/1.1
Host: zh.lianjia.com
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: https://www.baidu.com/link?url=4J5Kx--GLdLFESJhkfRePU8Ac_0agnTcOtB-b3kfnX8VNdZ_6TPqOyJGKVXkTczg&ck=6140.3.83.296.315.287.208.155&shh=www.baidu.com&sht=94886267_hao_pg&wd=&eqid=af98b98700060b77000000065aef0524
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-CA;q=0.8,en;q=0.7
Cookie: lianjia_uuid=ce61c41c-25b0-46d6-a0a0-d57a75ee8706; UM_distinctid=1631f588055f9-0286722badd3ec-b34356b-1fa400-1631f58805657f; _ga=GA1.2.43397143.1525239286; _smt_uid=5ae94e02.558be516; _jzqx=1.1525248800.1525335927.1.jzqsr=zh%2Elianjia%2Ecom|jzqct=/ershoufang/xiangzhouqu/.-; _jzqc=1; _jzqckmp=1; _gid=GA1.2.1028411676.1525594529; select_city=440400; all-lj=c60bf575348a3bc08fb27ee73be8c666; _qzjc=1; CNZZDATA1254525948=963210960-1525238218-https%253A%252F%252Fwww.lianjia.com%252F%7C1525608956; CNZZDATA1255633284=1054798284-1525238580-https%253A%252F%252Fwww.lianjia.com%252F%7C1525608969; lianjia_ssid=c046ddb3-3e66-4809-998a-52ade335fdfc; _qzja=1.1070225156.1525239298260.1525603274282.1525613866775.1525609113492.1525613866775.0.0.0.92.9; _qzjto=29.3.0; _jzqa=1.3750161754444366000.1525239284.1525603274.1525613867.9; _jzqy=1.1525239284.1525613867.3.jzqsr=baidu.jzqsr=baidu; Hm_lvt_9152f8221cb6243a53c83b956842be8a=1525607433,1525607626,1525609113,1525613867; Hm_lpvt_9152f8221cb6243a53c83b956842be8a=1525613867; _qzjb=1.1525613866775.1.0.0.0; _jzqb=1.1.10.1525613867.1; CNZZDATA1255604082=964175865-1525237915-https%253A%252F%252Fwww.lianjia.com%252F%7C1525612833
''')
运行结果始终报错400(Bad Request),此处耗费了大量时间排查。最终的解决方案是逐条发送请求头,并在每条头部信息后添加\r\n换行符。
sock = socket.socket()
sock.connect(('zh.lianjia.com', 80))
sock.send('GET /ershoufang/ HTTP/1.1\r\n'.encode())
sock.send('Host: zh.lianjia.com\r\n'.encode())
sock.send('Connection: keep-alive\r\n'.encode())
sock.send('Cache-Control: no-cache\r\n'.encode())
sock.send('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n'.encode())
sock.send('Upgrade-Insecure-Requests: 1\r\n'.encode())
sock.send('User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36\r\n'.encode())
sock.send('Accept-Encoding: gzip, deflate, br\r\n'.encode())
sock.send('Cookie: lianjia_uuid=ce61c41c-25b0-46d6-a0a0-d57a75ee8706; UM_distinctid=1631f588055f9-0286722badd3ec-b34356b-1fa400-1631f58805657f; _ga=GA1.2.43397143.1525239286; _smt_uid=5ae94e02.558be516; _jzqx=1.1525248800.1525335927.1.jzqsr=zh%2Elianjia%2Ecom|jzqct=/ershoufang/xiangzhouqu/.-; _jzqc=1; _jzqy=1.1525239284.1525594526.2.jzqsr=baidu.jzqsr=baidu|jzqct=%E9%93%BE%E5%AE%B6; _jzqckmp=1; _gid=GA1.2.1028411676.1525594529; Hm_lvt_9152f8221cb6243a53c83b956842be8a=1525594526,1525594536,1525594804,1525595210; select_city=440400; all-lj=c60bf575348a3bc08fb27ee73be8c666; _qzjc=1; lianjia_ssid=99306d63-8ee5-a53c-a740-2d3021f3db2f; CNZZDATA1255604082=964175865-1525237915-https%253A%252F%252Fwww.lianjia.com%252F%7C1525602095; _jzqa=1.3750161754444366000.1525239284.1525594526.1525603274.8; CNZZDATA1254525948=963210960-1525238218-https%253A%252F%252Fwww.lianjia.com%252F%7C1525603556; CNZZDATA1255633284=1054798284-1525238580-https%253A%252F%252Fwww.lianjia.com%252F%7C1525603557; Hm_lpvt_9152f8221cb6243a53c83b956842be8a=1525606057; _jzqb=1.9.10.1525603274.1; _qzja=1.1070225156.1525239298260.1525597069547.1525603274282.1525605398368.1525606071025.0.0.0.86.8; _qzjb=1.1525603274282.9.0.0.0; _qzjto=23.2.0\r\n\r\n'.encode())
随后遇到的问题是响应始终为重定向,状态码为301。经过长时间排查,发现直接在浏览器地址栏输入网址或使用Fiddler抓包时,并未捕获到状态为301的包。最后,使用Fiddler的Composer功能输入http://zh.lianjia.com/ershoufang,成功捕获到301和200状态码,其中200状态对应的地址为https://zh.lianjia.com/ershoufang,如下图所示。

至此明确了原因,即HTTP与HTTPS协议的区别。(实际上在301状态码的响应中,Location字段已包含目标地址,但因一个's'字符不显眼而被忽略,导致排查耗时较长)。
接下来只需掌握如何发送HTTPS请求即可。代码如下,主要修改了Socket建立和连接的部分。请注意,HTTPS请求的端口号应为443。参考文章见。
sock = ssl.wrap_socket(socket.socket())
sock.connect(('zh.lianjia.com', 443))
感觉自己在此类底层知识上了解尚浅,目前学校课程也未深入讲解应用层协议。后续将继续深入研究,如有错误或疏漏,欢迎指正。
总结
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对本站的支持。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









