python爬虫入门基本知识基础知识HTTP协议我们浏览网页的浏览器和手机应用客户端与服务器通信几乎都是基于HTTP协议,而爬虫可以看作是一个另类的客户端,它把自己伪装成浏览器或者手机应用客户端,按照自己的逻辑贪婪的向服务器索取数据,如何向服务器索取数据,所以了解HTTP协议就显得很有必要了。HTTP协议中文名称是超文本传输协议,是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式。请求和响应模式很好理解,客户端发送请求,服务器响应客户端的请求,就像学校食堂打菜一样,你和打菜阿姨说要哪份菜,她才会给你盛哪份菜。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。形象点说,可以把服务器看成是没有记忆的大学食堂打饭打菜,在每次请求中,阿姨并不知道你之前有没有打过菜,也不知道你是不是合法的学生,所以你只能一边举着学生证一边和阿姨说我要这个菜,阿姨看到你的学生证后才会给你打菜,而这个学生证就是你每次需要重传的数据信息。当我们在浏览器地址栏中输入并敲入回车后,浏览器会构造HTTP请求发送到服务器,在收到服务器的HTTP响应后,浏览器会解析页面,继续向服务器请求图片、视频、js脚本等数据,直到页面加载完成,最终展示给我们的就是B站主页了。这是我用Fiddler抓的包,展示的是HTTP最原生的面貌,接下来我就根据这张图具体的讲解HTTP协议,以及写爬虫需要关注的一些点。HTTP请求由三部分组成,分别是:请求行、消息报头、请求正文。在接收和解释请求消息后,服务器返回一个HTTP响应消息,HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文。HTTP方法HTTP请求的请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本。请求方法都是大写,有很多种,常见的有GETPOSTDELETEPUT,各种方法之间的区别不大。这里罗列了一些常用的方法,一般来讲,GET表示向服务器请求URI对应的资源,POST表示向服务器提交数据,DELETE表示删除数据,PUT表示修改数据。但这都是一种约定,没有强制的要求,如果你碰见用DELETE方法提交数据也没必要大惊小怪。在实际写爬虫的过程中,我们只需要按照抓包请求构造数据即可,没有必要在意用了什么方法。报头字段重点讲解几个写爬虫需要关注的字段User-Agent出现在请求报头中,表示客户端的操作系统、浏览器型号版本等信息。服务器可以根据此报头向客户端返回不同的页面以适应客户端。有些网站(知乎)会校验此报头,不填写或者不主流的报头都不能拿到正常的页面。因此自己在写爬虫的时候最好将从浏览器中拷贝到代码中。Cookie出现在请求抱头中,前面我们说过HTTP是基于请求与响应模式并且无状态的协议,之前举了打菜阿姨的例子,Cookie就相当于每次请求中的学生证,它可以记录用户的身份信息。当我们自己写爬虫的时候,如果需要登陆,并且登陆又有验证码或者短信验证时,最简单的方法就是从浏览器中把cookie拷贝到爬虫中,就可以骗过服务器了。Set-Cookie出现在响应抱头中,让客户端更新页面关联的Cookie,还是拿食堂阿姨的例子,如果你的响应报头有这个字段,意思就是阿姨重新给你了一个学生证,下次打饭你得用最新的学生证,原来的学生证不好使。如果你在模拟浏览器或者客户端登陆,需要将此报头更新已有的Cookie,不过Scrapy和requests都可以自动更新,因此不需要你再手动设置。Content-Type标明请求正文或者响应正文的格式,客户端或者服务器会根据此字段选择合适的方式解析正文内容,以下是一些常见的值Content-Length标明请求正文或者响应正文的长度,在使用requests构造请求的时候,我们不需要显式的加上此字段,requests会根据请求正文自动计算添加。Content-Encoding在某些情况下,正文会讲过压缩后传输,此字段会指明压缩的类型(gzip和压缩参数)Transfer-Encoding如果正文内容过长,HTTP协议允许将此字段设置为chunked,然后分块传输响应正文Connection在HTTP1.1之前的版本,不支持持久连接,所谓的持久链接意思就是:HTTP协议一般通过TCP协议实现,客户端和服务器经过TCP三次握手建立连接,在请求和响应结束之后,此连接继续保持,当之后还有请求的时候,就不需要重新通过三次握手再建立连接,这样可以极大的降低客户端和服务器的IO负载。在自己写爬虫的时候,我们可以根据浏览器的抓包数据有选择的添加一些请求报头,其实大部分情况下都可以直接使用浏览器中的请求头,为了避免不必要的麻烦,尽可能像的模仿浏览器总是没有错的。响应码响应消息的第一行的状态行包括HTTP的协议版本、状态码、状态码含义。按照约定2xx表示请求成功3xx表示重定向4xx表示客户端错误(403Forbiden404NotFound)5xx表示服务器错误(502网关错误)爬虫开发一般来说开发爬虫的过程是这样的1.抓包分析获取数据的URL2.通过python从上一步的URL获取数据3.从上一步获取的HTML页面或者JSON数据中解析出感兴趣的数据4.存储数据下面就讲解这四个关键点抓包发包工具写爬虫的第一步就是分析想要的数据浏览器是通过什么URL拿到的,抓包也就在所难免。最好用的抓包工具当然是谷歌浏览器了,右键检查,选中网络,重新刷新页面就可以看到加载此网页所有的HTTP请求了,如果此链接有跳转地址,跳转之前的HTTP请求会被清掉,所以记得选上preservelog,尤其是登陆的时候,一般都会有跳转。再介绍另外两个HTTP抓包工具——Fiddler和Charles,分别在windows和macos使用。它们可以为我们展示更多HTTP的细节,将请求和响应都调至Raw模式下,我们就可以一睹HTTP请求和响应的真实面貌。通过抓包分析出具体的URL后,想进一步确认自己构造的参数和报头能否正确获取到数据,应该怎么做呢?不怕,postman可以帮你,你可以很轻松的选择方法,定义header,添加各种类型的body。python请求数据讲完了基本的HTTP协议知识后,大家可能会疑问那我该如何模仿浏览器或者手机客户端去向服务器发送HTTP请求呢?python的原生库urllib、第三方库requests、pycurl等都支持HTTP协议,既然有这么多工具可以用,大家可能就又有疑问该选择哪个工具了。在此我特地安利大家用一下requests,它让爬虫变得如此简单,让你再也不用为字符编码、重定向、cookie、响应解压缩烦恼了。如果你坚持用原生的库,那么有以下问题需要你一一解决,这些都是当年自己趟过的坑,绝非危言耸听。1.需要自己判断服务器返回数据的编码格式,如果这个地方你不能正确判断,那恭喜你之后的每一步,你都必须面对乱码的问题2.重定向,urllib不能自动判断重定向,需要自己解析重定向的链接并重新请求3.如果模拟登陆,你必须要手动保证Cookie正确更新和发送4.很多情况下响应正文是压缩过的,需要做解压处理5.对于比较长的响应正文,服务器会将正文分段传输,所以还需要你做拼接操作6.原生的urllib对HTTPS和持久连接都支持不好当你花了一整天,写了好几百行的代码终于解决上面的问题后,而你旁边的同事可能早已经把数据下载完并愉快的约妹子去了。所以用requests吧,兄弟们用了都说好。下面我用两个例子讲解一下如何用requests获取想要的数据,并教你如何解决这些问题:如何发送不同方法的请求如何保存cookie如何添加代理如何处理编码问题B站假如我想下载B站里面某位小姐姐所有上传的视频,应该怎么办呢?首先你需要找到这位小姐姐的视频主页但是通过谷歌浏览器右键查看页面源码,没有从html中找到这些视频的播放信息,唯一的可能就是视频数据是通过js脚本调用服务器获取,然后生成的这张页面。爬虫小白可能会疑问,难道我需要像浏览器一样分析js脚本,然后模拟js执行吗?其实不用这么复杂,只需要简单的分析抓包结构,就可以找到请求URL了。获取视频的URL:=79415852&pagesize=30&tid=0&page=1&keyword=&order=senddate那么问题又来了,这个URL的其他参数是干啥的呢?凭经验,mid肯定是这位小姐姐的用户id,page和pagesize是负责分页用的,keyword和是用来搜索的关键字,order是排序选项,剩下的tid是干啥的呢?其实写爬虫很多时候都会遇到这种问题,不知道某个参数的含义,也不确定正确的取值范围,需要一些尝试和运气,这里我们不管它就好。而返回的字段中有一个aid,那肯定是视频的id,有这个就可以自己拼接出播放链接了。是不是很简单,通过response.ok查看请求是否正确返回,因为此接口的数据为json格式,直接通过response.json()就可以直接拿到格式化的数据。知乎虽然现在知乎对未登录用户展示的内容越来越多,但是仍会有一些限制,用爬虫模拟登陆可以之后再去爬取数据,可以避免很多不必要的麻烦,现在就讲一讲如何用requests模拟用户登陆。还是和之前一样,在登陆页面打开谷歌浏览器的抓包窗口,输入用户名和密码点击确定,然后在茫茫请求中找到发送登陆信息的那个HTTP请求即可,功夫不负有心人,我们终于找到了登陆的请求。等等,请求里面还有一个_xsrf,这是一个什么鬼参数,其实呢这是一个防止跨站请求伪造而生成的一个随机数,可以通过解析页面获取,这一部分我在下面会讲解如何HTML获取数据,现在假设我们已经拿到这个数据了,如何将用户名和密码登陆呢?如果我们想要自动保存Cookie信息,只需要生成一个Session对象,之后所有的请求通过此对象完成,requests会像浏览器一样自动更新cookie信息,并在每次请求的时候加上cookie,因此在成功的发送post登陆请求之后,就可以用session在保持登陆状态请求数据了。需要注意的是在请求的时候我特意去掉了Cookie和Content-Length报头,因为requests会自动加上,所以不需要我们特意关注。更多关于requests的使用可以查看官方文档:英文:中文:解析数据因为个人在解析数据的时候遇到过很多编码的坑,所以在继续讲解之前告诉大家一些如何避免编码问题的方法。python2中有两种字符串:unicode和str,它们分别对应python3中的str和bytes。如何定义这两种类型的变量在下图中给大家列出来了。以python3为例讲解这两种类型的区别。python3中的str每一个字符可以存储一个英文字母、一个汉字甚至一个emoji表情,它可以通过特定的编码方式,例如utf-8或者gbk生成bytes,在不同的编码格式下,可能需要2-3个字符常能表示一个汉字。bytes可以指定解码格式解码生成str,如果指定的解码格式不匹配,就会导致乱码问题。为了避免乱码问题,最好的方式就是使用str,等到需要写入文件或者数据库的时候,再指定写入的编码格式,用好这个准则,我们可以避免百分之九十的编码问题。HTTP响应的数据格式有很多,例如文本、json、html,对应的解析方式也很多。通用一点,用python内置库正则匹配找到想要的数据,但是这种方法相对来说比较麻烦,而且不好维护,比较适