简述:较为底层的爬虫实现,用于了解爬虫底层实现的具体流程,现在各种好用的爬虫库(如requests,httpx...等)都是基于此进行封装的。

PS:本文只作为实现请求的代码记录,基础部分不做过多阐述。

一、什么是socket

简称:套接字

大白话陈述一下:网络由类似无数终端(计算机设备)组成,每一台计算机设备上面都有很多的程序,而每个程序都有其端口号,ip+端口号形成唯一标识,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。PS:个人总结的,可能不是很准确看看就行

二、socket实现http请求

http请求方式:get和post

要使用socket首先要实现其实例化:scoket(family,type[,protocal])

其中第一个参数family表示地址族,常用的协议族有:AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE等,默认值为AF_INET,通常使用这个即可。

第二个参数表示Socket类型,这里使用的值有三个:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW。

SOCK_STREAM:TCP类型,保证数据顺序以及可靠性;

SOCK_DGRAM:UDP类型,不保证数据接收的顺序,非可靠连接;

SOCK_RAW:原始类型,允许对底层协议如IP、ICMP进行直接访问,基本不会用到。

默认值是第一个。

第三个参数是指定协议,这个是可选的,通常赋值为0,由系统选择。

  1. GET /article-types/6/ HTTP/1.1
  2. Host: www.zhangdongshengtech.com
  3. Connection: close

第一行:请求方式+空格+请求的资源地址+空格+http协议版本

第二行:明确host

第三行:定义了Connection的值是close,如果不定义,默认是keep-alive。

PS:这里Connection设置为close是为了方便获取完响应后直接断开连接,如果是keep-alive再获取完响应数据后不会断开连接。

报文示例如下:目标url=http://www.baidu.com

GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n

报文解析

一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成

请求行:由请求方法字段、URL字段和HTTP协议版本字段3个字段组成,它们用空格分隔。例如,GET /index.html HTTP/1.1

请求头部:请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。例如,User-Agent:产生请求的浏览器类型

空行:最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。

请求数据:请求数据不在GET方法中使用,而是在POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求数据相关的最常使用的请求头是Content-Type和Content-Length。

GET:示例

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2023/1/8 15:12
  3. # @Author : 红后
  4. # @Email : not_enabled@163.com
  5. # @blog : https://www.cnblogs.com/Red-Sun
  6. # @File : socket_http_get.py
  7. # @Software: PyCharm
  8. import socket
  9. url = 'www.baidu.com'
  10. port = 80
  11. # 创建TCP socket
  12. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  13. # 连接指定服务端
  14. sock.connect((url, port))
  15. # 创建请求消息头发送的请求报文(详情见浏览器里面的请求头原文)
  16. request_url = 'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n'
  17. # 发送请求(send的参数为字节格式所以进行了编码)
  18. sock.send(request_url.encode())
  19. response = b''
  20. # 接收返回的数据
  21. rec = sock.recv(1024)
  22. # 由于是TCP协议所以是分片传递,用while循环获取数据
  23. while rec:
  24. response += rec
  25. rec = sock.recv(1024)
  26. # 返回响应的文本是字节格式需要解码
  27. print(response.decode())



由上个演示可知响应中有Content-Length存在因此就可以知到响应数据的字节长度了。

所以上述示例代码可以修改为通过判断接受报文总字节长度达标后断开连接。

自动判断响应长度示例代码

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2023/1/8 15:56
  3. # @Author : 红后
  4. # @Email : not_enabled@163.com
  5. # @blog : https://www.cnblogs.com/Red-Sun
  6. # @File : socket_http_get_upgrade.py
  7. # @Software: PyCharm
  8. import socket
  9. url = 'www.baidu.com'
  10. port = 80
  11. # 创建TCP socket
  12. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  13. # 连接指定服务端
  14. sock.connect((url, port))
  15. # 创建请求消息头发送的请求报文(详情见浏览器里面的请求头原文)
  16. request_url = 'GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n'
  17. # 发送请求(send的参数为字节格式所以进行了编码)
  18. sock.send(request_url.encode())
  19. # 接收返回的数据
  20. rec = sock.recv(1024)
  21. # 获取消息头与消息体分割的索引位置
  22. index = rec.find(b'\r\n\r\n')
  23. # 换行前为响应头报文
  24. response_head = rec[:index]
  25. # 索引位后移4为删除换行获取响应文本主体
  26. response_body = rec[index+4:]
  27. # 获取Content-Length,start_index:索引开始位,end_index:索引结束位
  28. start_index = response_head.find(b'Content-Length')
  29. end_index = response_head.find(b'\r\n', start_index)
  30. content_length = int(response_head[start_index:end_index].split(b' ')[1])
  31. # 由于是TCP协议所以是分片传递,用while循环获取数据
  32. while len(response_body) < content_length:
  33. rec = sock.recv(1024)
  34. response_body += rec
  35. # 关闭套接字
  36. sock.close()
  37. # 返回响应的文本是字节格式需要解码
  38. print(response_body.decode())

POST:示例

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2023/1/9 14:33
  3. # @Author : 红后
  4. # @Email : not_enabled@163.com
  5. # @blog : https://www.cnblogs.com/Red-Sun
  6. # @File : socket_http_post.py
  7. # @Software: PyCharm
  8. import socket
  9. from urllib.parse import urlparse
  10. url = 'http://www.baidu.com/'
  11. # 作为一个懒癌患者加上了一个主域名解析,就不需要自己提取了,直接把目标网站扔里面结束。
  12. host_url = urlparse(url).hostname
  13. # http请求大部分都是80端口
  14. port = 80
  15. # 创建TCP socket
  16. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  17. # 连接指定服务端
  18. sock.connect((host_url, port))
  19. request_header = b'''POST / HTTP/1.1
  20. Accept: application/json, text/javascript, */*; q=0.01
  21. Accept-Language: zh-CN,zh;q=0.9
  22. Cache-Control: no-cache
  23. Connection: close
  24. Content-Length: 传递参数的长度
  25. Content-Type: application/x-www-form-urlencoded
  26. Host: www.baidu.cn
  27. Origin: http://www.baidu.cn
  28. Pragma: no-cache
  29. Referer: http://www.baidu.cn/
  30. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
  31. X-Requested-With: XMLHttpRequest
  32. '''
  33. data = b'传递的参数(原型格式)'
  34. # 发送请求(send的参数为字节格式所以进行了编码)
  35. sock.send(request_header + b'\r\n' +data)
  36. response = b''
  37. # 接收返回的数据
  38. rec = sock.recv(1024)
  39. # 由于是TCP协议所以是分片传递,用while循环获取数据
  40. while rec:
  41. response += rec
  42. rec = sock.recv(1024)
  43. # 返回响应的文本是字节格式需要解码
  44. print(response.decode())

PS:避免实例网站被搞,这里就用百度代替,此代码只是模板链接无法直接运行。

url:目标网站地址

request_header:发起的请求头(懒人做法直接去浏览器抓包后复制请求头的值就行,直接粘贴就能用),这个模板要改几个值,Connection需要是close,浏览器默认keep-alive,如果Accept-Encoding属性中有gzip最好移除,不然打印容易报错。

三、socket实现https请求

http跟https的一些区别:

端口:http默认80,https默认443

传递:HTTP协议是位于第四层协议TCP之上完成的应用层协议, 端到端都是明文传送,别人一下就能获取到数据,不太安全。HTTPS是基于SSL加密传输的,这样别人截获你的数据包破解的概率要小一点,比HTTP安全一些。

PS:个人看法,http跟https相比就差了一个ssl加密所以用ssl.wrap_socket (sock[, **opts])包装现有套接字实现https请求。

https的GET实例

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2023/1/9 15:34
  3. # @Author : 红后
  4. # @Email : not_enabled@163.com
  5. # @blog : https://www.cnblogs.com/Red-Sun
  6. # @File : socket_https_get.py
  7. # @Software: PyCharm
  8. import socket
  9. import ssl
  10. url = 'www.baidu.com'
  11. port = 443
  12. # 勇1ssl.wrap_socket封装socket
  13. sock = ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM))
  14. # 连接指定服务端
  15. sock.connect((url, port))
  16. # 创建请求消息头发送的请求报文(详情见浏览器里面的请求头原文)
  17. request_url = b'GET https://www.baidu.com/ HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n'
  18. # 发送请求(send的参数为字节格式所以进行了编码)
  19. sock.send(request_url)
  20. response = b''
  21. # 接收返回的数据
  22. rec = sock.recv(1024)
  23. # 由于是TCP协议所以是分片传递,用while循环获取数据
  24. while rec:
  25. response += rec
  26. rec = sock.recv(1024)
  27. # 返回响应的文本是字节格式需要解码
  28. print(response.decode())

https的POST实现:就是对上面http的post实现就行ssl封装这里就不在做过多阐述了。

懒人直接用脚本

个人封装的脚本,主要作为学习熟练编写还有很多能封装改进的地方,暂时简化如此了,要用的话看一下注意事项

注意事项:

  1. url要带http或https
  2. post传参的data数据前面记得加个r避免\斜杠的转义效果导致报错
  3. 复制的request_header粘贴之后要看看有没有多余的空行有时候粘贴会自动回车
  4. 访问失败可以尝试删减request_header中的参数,有时候无用参数会导致这类问题

PS:暂时就想到这么多等以后遇见问题在补充吧,毕竟也只是学习笔记,慢慢学吧。

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2023/1/9 15:37
  3. # @Author : 红后
  4. # @Email : not_enabled@163.com
  5. # @blog : https://www.cnblogs.com/Red-Sun
  6. # @File : SocketSpider.py
  7. # @Software: PyCharm
  8. import socket
  9. import ssl
  10. from urllib.parse import urlparse
  11. class SocketSpider:
  12. '''
  13. socket爬虫脚本(PS:模仿requests写的仅作为学习所用)
  14. '''
  15. def format_socket(self, url):
  16. '''
  17. 格式化创建socket
  18. '''
  19. host_url = urlparse(url).hostname
  20. port = (80, 443)['https' == url[:5]]
  21. if port == 80:
  22. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  23. else:
  24. sock = ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM))
  25. sock.connect((host_url, port))
  26. return sock
  27. def format_header(self, request_header):
  28. '''
  29. 对请求头中的数据进行处理
  30. '''
  31. message = b''
  32. for header_data in request_header.split(b'\n'):
  33. if b'gzip' in header_data:
  34. continue
  35. message += header_data + b'\r\n'
  36. # message += b'\r\n'
  37. return message.replace(b'Connection: keep-alive', b'Connection: close')
  38. def get(self, url: str, request_header: str):
  39. '''
  40. get请求实现
  41. url: 必须要带http或https
  42. request_header: 直接从浏览器抓包复制出请求头就行
  43. '''
  44. sock = self.format_socket(url=url)
  45. message = self.format_header(request_header.encode())
  46. sock.send(message)
  47. response = b''
  48. # 接收返回的数据
  49. rec = sock.recv(1024)
  50. # 由于是TCP协议所以是分片传递,用while循环获取数据
  51. while rec:
  52. response += rec
  53. rec = sock.recv(1024)
  54. # 返回响应的文本是字节格式需要解码
  55. return response.decode()
  56. def post(self, url:str, request_header: str, data: str):
  57. '''
  58. post请求实现
  59. url: 必须要带http或https
  60. request_header: 直接从浏览器抓包复制出请求头就行
  61. data: 同样直接从浏览器复制出来就行(前面最好加上r避免\斜杠的转义效果)
  62. '''
  63. sock = self.format_socket(url=url)
  64. message = self.format_header(request_header.encode()) + data.encode()
  65. sock.send(message)
  66. response = b''
  67. # 接收返回的数据
  68. rec = sock.recv(1024)
  69. # 由于是TCP协议所以是分片传递,用while循环获取数据
  70. while rec:
  71. response += rec
  72. rec = sock.recv(1024)
  73. # 返回响应的文本是字节格式需要解码
  74. return response.decode()
  75. if __name__ == '__main__':
  76. url = 'https://www.baidu.com/'
  77. request_header = '''GET / HTTP/1.1
  78. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
  79. Accept-Encoding: gzip, deflate, br
  80. Accept-Language: zh-CN,zh;q=0.9
  81. Cache-Control: no-cache
  82. Connection: keep-alive
  83. Cookie: PSTM=1664529301; BIDUPSID=5D9256DF2D0A67E0B97C22A9AC33CB09; BAIDUID=B54EAE77A79A6008061229C7A4AB5387:FG=1; MCITY=-224%3A; BD_UPN=12314753; BAIDUID_BFESS=B54EAE77A79A6008061229C7A4AB5387:FG=1; __bid_n=1845a26a63492b76484207; FEID=v10-1cec01417c6a9c7b08b333c03eb5425b40157d14; __xaf_fpstarttimer__=1672974585279; __xaf_thstime__=1672974585296; FPTOKEN=1oofVJhUqwLSMyqmyHnXJSxhD+K991pfK+BFWKI9hSqaoAEyXVZYBO320OdxlRccUo1KnxIfZK0C7Fpcc5ED9MHoxVt7PAEblzQAudnC7Di7Ic5it9yUwuAcV2iTjxygfDUcIQI5H0vbTllVkUpaX+xEyctdNFuFm0Py4wQ6EFX7bAjD0I5muVjmnRdsC/6qHg8fINmN3dEID5+n6zpiNVs+bLYePtIeO9SI/HvE7mnQxcd/HbOwf0to5q/5tcNyzO8riRarry1ryI7wocSoAnYc6HTyEaVY2xSdDYeXPNLCbMOwZvfAejftHAfvTCNRWO/K3GV0PYU/+yhEXkejECxAMOnFsbfPyhHd13AyEY0IwyNNYthFeO5qSazdfMZIVFEqenGxifjUCLsejVbcHg==|4j1jFPC8s2KJ3Bd5vfyJK+BhAEWpSgq+RbmgPQ512ro=|10|b66cd1b11d1528a08711913e6ce61baf; __xaf_fptokentimer__=1672974585330; BA_HECTOR=24ag80a4ah2la5ak05242lqk1hrnc8s1k; ZFY=gDQKa:Bq4ufkSfg5tno3jSIyvxsB0KQkWtgWw1CAvnO4:C; BDORZ=FFFB88E999055A3F8A630C64834BD6D0; COOKIE_SESSION=4_0_9_9_20_16_1_1_9_8_1_2_1125999_0_3_0_1673244971_0_1673244968%7C9%231882534_14_1670467865%7C4; BDRCVFR[t1epCe_UAtt]=OjjlczwSj8nXy4Grjf8mvqV; BD_HOME=1; delPer=0; BD_CK_SAM=1; PSINO=5; ab_sr=1.0.1_MjQ0YjU1ODM4ODAzOTE5ODQxM2U5ZTRhYjFlMDIwMzkwZGIzYjc0MjVmZmExMTZmMTA1ZmEzMmUzNGMyMGQ3ZThmNzliNmJhZGFjMDM3NDE4YzQ4NGMxMWI0YzIxY2VkMWFhNTcyMTQ1NWVlNDMyY2VkZmY5MTMwZjJmNDVhOTkwNzY1YTQwOTI4M2I2Yzg1ZTYwODBmYzhiZTgzMzA1MA==; BDRCVFR[sOxo1TgcNNt]=OjjlczwSj8nXy4Grjf8mvqV; BDRCVFR[bIKc3BPCk_C]=OjjlczwSj8nXy4Grjf8mvqV; H_PS_645EC=4e43o0N5BrE9ePh0PWMUS4TfxCmDRlE97BtwBPR4eHZmdOKlWsRxZaGgaEJYE2x9oTHCsGqhpL13; H_PS_PSSID=36555_37647_37625_36920_38035_37989_37931_26350_38009_37881
  84. Host: www.baidu.com
  85. Pragma: no-cache
  86. Referer: https://cn.bing.com/
  87. Sec-Fetch-Dest: document
  88. Sec-Fetch-Mode: navigate
  89. Sec-Fetch-Site: cross-site
  90. Sec-Fetch-User: ?1
  91. Upgrade-Insecure-Requests: 1
  92. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
  93. sec-ch-ua: "Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"
  94. sec-ch-ua-mobile: ?0
  95. sec-ch-ua-platform: "Windows"
  96. '''
  97. a = SocketSpider()
  98. a.get(url=url, request_header=request_header)

Socket爬虫:Python版的更多相关文章

  1. 百度图片爬虫-python版-如何爬取百度图片?

    上一篇我写了如何爬取百度网盘的爬虫,在这里还是重温一下,把链接附上: http://www.cnblogs.com/huangxie/p/5473273.html 这一篇我想写写如何爬取百度图片的爬虫 ...

  2. 百度图片爬虫-python版

               self.browser=imitate_browser.BrowserBase()            self.chance=0            self.chanc ...

  3. 豆瓣top250(go版以及python版)

      最近学习go,就找了一个例子练习[go语言爬虫]go语言爬取豆瓣电影top250,思路大概就是获取网页,然后根据页面元素,用正则表达式匹配电影名称.评分.评论人数.原文有个地方需要修改下patte ...

  4. Atitit 爬虫 node版 attilaxA

    Atitit 爬虫 node版 attilax 1.1. 貌似不跟python压实,,java的webmagic压实,,什么爬虫框架也没有,只好自己写了. 查了百度三爷资料也没有.都是自己写.. 1. ...

  5. socket编程python+c

    python版: server: def socket_loop_server_function(): HOST = '192.168.56.1' PORT = 21567 sk = socket.s ...

  6. Python版:Selenium2.0之WebDriver学习总结_实例1

    Python版:Selenium2.0之WebDriver学习总结_实例1  快来加入群[python爬虫交流群](群号570070796),发现精彩内容. 实属转载:本人看的原文地址 :http:/ ...

  7. 【原】Learning Spark (Python版) 学习笔记(三)----工作原理、调优与Spark SQL

    周末的任务是更新Learning Spark系列第三篇,以为自己写不完了,但为了改正拖延症,还是得完成给自己定的任务啊 = =.这三章主要讲Spark的运行过程(本地+集群),性能调优以及Spark ...

  8. 数据结构:顺序表(python版)

    顺序表python版的实现(部分功能未实现) #!/usr/bin/env python # -*- coding:utf-8 -*- class SeqList(object): def __ini ...

  9. python版恶俗古风自动生成器.py

    python版恶俗古风自动生成器.py """ python版恶俗古风自动生成器.py 模仿自: http://www.jianshu.com/p/f893291674c ...

  10. LAMP一键安装包(Python版)

    去年有出一个python整的LAMP自动安装,不过比较傻,直接调用的yum 去安装了XXX...不过这次一样有用shell..我也想如何不调用shell 来弄一个LAMP自动安装部署啥啥的..不过尼玛 ...

随机推荐

  1. uni-app 如何优雅的使用权限认证并对本地文件上下起手

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 1.起因 最近有一个需求,需要使用自定义插件,来对接硬件功能,需要配合对手机的权限进行判断和提示,并在对接后对本地文件进行操作,这里给大家 ...

  2. Vue3.x+element-plus+ts踩坑笔记

    闲聊 前段时间小颖在B站找了个学习vue3+TS的视频,自己尝试着搭建了一些基础代码,在实现功能的过程中遇到了一些问题,为了防止自己遗忘,写个随笔记录一下嘻嘻 项目代码 git地址:vue3.x-ts ...

  3. .NET中IActionResult的返回类型

    ActionResult继承了IActionResult JsonResult.RedirectResult.FileResult.ViewResult.ContentResult均继承了Action ...

  4. JVM堆内存转储

    在发生内存溢出错误 java.lang.OutOfMemoryError 时, JVM自动执行堆内存转储,以方便事后进行排查和分析. JVM提供了一个命令行启动参数 HeapDumpOnOutOfMe ...

  5. 用于数据科学的顶级 C/C++ 机器学习库整理

    用于数据科学的顶级 C/C++ 机器学习库整理 介绍和动机--为什么选择 C++ C++ 非常适合 动态负载平衡. 自适应缓存以及开发大型大数据框架 和库.Google 的MapReduce.Mong ...

  6. java学习之JSP

    0x00前言 JSP:全拼写:java Server pages:java 服务器端页面 可以理解为一个特殊的页面:可以定义html代码也可以定义java的代码 定义:JSP是简化Servlet编写的 ...

  7. MAUI新生-XAML语法基础:语法入门Element&Property&Event&Command

    一.XAML(MAUI的XAML)和HTML 两者相似,都是标签语言(也叫标记)组成的树形文档.每个标签元素,可视为一个对象,通过"键=值"形式的标签属性(Attribute),为 ...

  8. 7 款殿堂级的开源 CMS(内容管理系统)

    最近,有读者留言让我推荐开源 CMS.我本想直接回复 WordPress,但是转念一想我玩 WordPress 是 2010 年左右的事情了,都过去十年了,它会不会有些过时呢?有没有新的.更好玩的开源 ...

  9. 【Android逆向】rpc调用某安App的X-App-Token签名函数

    阅读此文档的过程中遇到任何问题,请关注公众号[移动端Android和iOS开发技术分享]或加QQ群[309580013] 1.目标 在学习的过程中,会遇到有些算法比较麻烦,没有办法直接还原.那我们就另 ...

  10. 1759D(数位变0)

    题目链接 题目大意: 给你两个整数n, m.你需要求一个数,它满足如下条件: 是n的整数倍,且倍数小于m. 你应该使其末尾的0尽可能的多(如100后面有2个零,1020后面有一个零,我们应该输出100 ...