前面http和https的介绍,可直接跳过看下面的案例
HTTP与HTTPS 为什么要先简要复习一下http和https?因为要发送请求,模拟浏览器,获取和浏览器一模一样的响应。
HTTP:超文本传输协议,默认端口:80,是一种建立在TCP上的无状态连接,说白了就是个协议,规定了一些列的规则,整个基本的工作流程是客户端发送一个HTTP请求,说明客户端想要访问的资源和
请求的动作,服务端收到请求之后,服务端开始处理请求,并根据请求做出相应的动作访问服务器资源,最后通过发送HTTP响应把结果返回给客户端。
HTTPS:即HTTP+SSL(安全套接字层),默认端口号:443,即在HTTP上多了个安全套接字层SSL,SSL是一个提供数据安全和完整性的协议,负责网络连接的加密。
题外话:HTTPS通信中的几个概念:加密分为对称和非对称 对称加密:信息的加密和解密都是通过同一个密钥进行的,实际通信中,一个服务器可以同时对应好几个客户端,也就是A客户端获得的加解密算法同样可以加解密B客户端 的消息,这跟没加密一样,为了防止这种情况,就只能不同的客户端使用不同密钥,这样的话密钥将会有很多,并且在通信刚开始,服务器和A客户端就要协商好用什么密钥,而这个协商过程是不能加密 的,不然A客户端就读不懂服务器消息了,因此还是存在风险。
非对称加密:应用最广的加密机制“非对称加密”,特点是私钥加密后的密文,只要是公钥,都可以解密,但是反过来公钥加密后的密文,只有私钥可以解密。私钥只有一个 人有,而公钥可以发给所有的人。所以公钥不需要加密,而私钥只存在于服务器。这样只需要一套公钥和私钥就可以了。那么现在的问题是,如何让客户端安全的获取公钥?如果服务器直接明文发送给客户 端,那么可能发生被劫持的情况,例如客户端A和服务器S通信,S给A的公钥被B劫持后修改了,那么之后A将会用假的公钥进行加密,将消息发给服务器时B继续劫持,查看内容或者修改之后用真公钥加密然 后发给服务器,这就是中间人攻击。这时候风险依然存在,而为了解决这个问题,采用了一种SSL 证书(需要购买)和CA机构的方法,涉及防伪和证书链的概念,大概流程是: 在客户端第一次请求服务器时,服务器发送回一个SSL证书给客户端,SSL 证书中包含的具体内容有证书的颁发机构的证书 、有效期、公钥 、证书持有者、签名 。防伪的步骤:浏览器 拿到这个证书后读取证书中的证书所有者、有效期等信息进行一一校验,开始查找操作系统中已内置的受信任的证书发布机构CA,与服务器发来的证书中的颁发者CA比对,用于校验证书是否为合法机构颁 发,如果没找到,则报错,如果找到了,浏览器会从操作系统中取出颁发者CA的公钥,然后对服务器发来的证书里面的签名 进行解密,如果能够解密则一定是证书颁发机构颁发的证书如果解密后信息 与Server信息一致则确实是颁发给该Server的,综上校验通过,这就是防伪的流程,而证书链的作用可以保证正在通信的Server确实是证书颁发机构指定的Server,流程是:通过和Server证书验证同 的过程通过内嵌在浏览器或者JDK中的根证书验证下中级证书的合法性就好了。因为根证书是内嵌的具有绝对的合法性,如果根证书信任该中级机构,则该中级证书颁发的证书也是可信的这就是证书链了。 这样通过第三方的校验保证了身份的合法,解决了公钥获取的安全性。什么你问我怎么申请CA证书?国内的阿里云和腾讯云上找去
举个栗子:爬取某贴吧前1000页内容 先来一个简单的例子熟悉一下api和爬虫基本流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import requestsclass TiebaSpider : def __init__ (self, tieba_name) : self.tieba_name = tieba_name self.url_temp = "https://tieba.baidu.com/f?kw=" + tieba_name + "&ie=utf-8&pn={}" self.headers={"User-Agent" :"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36" } def get_url_list (self) : return [self.url_temp.format(i*50 ) for i in range(1000 )] def parse_url (self, url) : print(url) response = requests.get(url, headers=self.headers) return response.content.decode() def save_html (self, html_str, page_num) : file_path = "{}第{}页.html" .format(self.tieba_name, page_num) with open(file_path, "w" ,encoding="utf-8" ) as f: f.write(html_str) def run (self) : url_list = self.get_url_list() for url in url_list: html_str = self.parse_url(url) page_num = url_list.index(url)+1 self.save_html(html_str, page_num) if __name__ == '__main__' : tieba_spider = TiebaSpider("李毅" ) tieba_spider.run()
这里爬取下来的内容是没问题的,但保存下来的html直接打开是有问题的,因为万恶的百度在其中把大量有用内容添加了注释,后续是通过js来去掉注释的。这里暂时不处理,只展示基本api的使用。
发送POST请求 什么时候需要发送post请求?
登录注册等有敏感信息的时候,POST比GET更安全
需要传输大文本的时候(POST请求对数据长度没有要求)python 1 response = requests.post("",data=data,headers=headers)
使用代理 为什么爬虫需要代理?
让服务器以为不是同一个客户端在不断请求
防止我们真实地址倍泄露,防止被追究
python 1 response = requests.get("url",proxies=proxies)
proxies是一个字典,
1 2 3 4 proxies={ "http" :"http://xx.xx.xx.xx:xxx" , "https" :"https://xx.xx.xx.xx:xxxx" , }
request模拟登陆 cookie和session区别:
cookie数据存放在客户的浏览器上,session数据存放在服务器上
cookie不安全,别人可以分析存放在本地的cookie并进行cookie欺骗
session会在一定时间内保存在服务器上,当访问增多,会比较占用服务器性能
单个cookie保存的数据不能超过4k,很多浏览器会限制一个站点最多保存20个cookie
1)request提供了一个session类,来实现客户端和服务器的会话保持,先实例化一个session,用session发送post请求登陆网站,把cookie保存在session中,再使用session请求登陆之后才能访问的网站,session能够自动的携带登陆成功时保存在其中的cookie。 使用方法:
1 2 session = requests.session() response = session.get(url,headers)
2)如果请求页面时不发送post请求的情况下,可以在headers中添加cookie键值对,要注意cookie的有效时间
3)也可以把cookie作为request的参数。此时cookie参数时一个字典。
request小技巧
request.utils.dict_from_cookiejar(response.cookies) 把cookie转换为字典,request.utils.cookiejar_from_dict ,将字典转化为cookie
request.utils.unquote(“编码后的url”) 将url地址解码,反之quote为编码
忽视证书错误 request.get(“url”,verify=false)
设置超时 request.get(“url”,timeout)
刷新网页 使用第三方模块retrying,还可以定义最大重新请求次数
爬虫的基本套路
准备url
准备start_url
url地址规律不明显,总数不确定的情况下
通过代码提取下一页的url地址
当下一页的地址在网页的响应中时,可以通过xpath
通过其他方式寻找url地址,比如通过js生成,这种情况下部分参数是在当前响应中
准备url_list
发送请求,获取响应
添加随机的User-Agent,防反爬虫
添加随机的代理ip,防反爬虫
在对方判断出是爬虫之后,应该添加更多的header字段,包括cookies
cookie可以用session来解决
如果不登录的话
准备能成功请求对方网站的cookie,即接收对方网站设置在response的cookie
下次请求的时候,使用之前的列表中的cookie来请求
如果登录的话
准备多个账号
获取多个账号cookie
之后随机选择cookie来请求登录之后才能访问的网站
提取数据
确定数据位置,确定数据是否在当前的url地址响应中
如果在当前url地址响应中
提取的是列表页的数据
提取的是详情页的数据
1.确定详情页url地址
2.发送请求
3.提取数据
4.返回
如果不在当前url响应中
数据的提取
xpath,从html中提取整块的数据,先分组,然后针对每一组进行提取
json
re,提取html中的json字符串或者某些容易用正则区分出的属性等
保存数据
一个小案例,爬去豆瓣上最近热播的英美剧,国产剧,动漫以及综艺的节目名称和评分 网页截图以及爬去后的效果图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 import requestsimport jsonclass DoubanSpider : def __init__ (self) : self.headers = { "User-Agent" : "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1" , "Referer" : "https://m.douban.com/tv/american" } self.url_temp_list = [ { "url_temp" : "https://m.douban.com/rexxar/api/v2/subject_collection/tv_american/items?start{}&count=18&loc_id=108288" , "type" : "英美剧" , }, { "url_temp" : "https://m.douban.com/rexxar/api/v2/subject_collection/tv_domestic/items?start{}&count=18&loc_id=108288" , "type" : "国产剧" , }, { "url_temp" : "https://m.douban.com/rexxar/api/v2/subject_collection/tv_animation/items?start{}&count=18&loc_id=108288" , "type" : "动漫" , }, { "url_temp" : "https://m.douban.com/rexxar/api/v2/subject_collection/tv_variety_show/items?start{}&count=18&loc_id=108288" , "type" : "综艺" , } ] self.curIndex=1 def parse_url (self, url) : print(url) response = requests.get(url, headers=self.headers) return response.content.decode() def get_content_list (self, json_str) : dict_ret = json.loads(json_str) content_list = dict_ret["subject_collection_items" ] total = dict_ret["total" ] return content_list, total def save_content_list (self, content_list) : with open("douban.txt" , "a" , encoding='utf-8' ) as f: for content in content_list: f.write(self.curIndex.__str__()+" :" + content["title" ]+" 评分:" +content["rating" ]["value" ].__str__()) f.write("\n" ) self.curIndex += 1 def run (self) : for url_temp in self.url_temp_list: with open("douban.txt" , "a" , encoding='utf-8' ) as f: f.write(url_temp["type" ] + ":================================================================" ) f.write("\n" ) self.curIndex = 1 num = 0 total = 1 while num < total + 18 : url = url_temp["url_temp" ].format(num) json_str = self.parse_url(url) content_list, total = self.get_content_list(json_str) self.save_content_list(content_list) num += 18 if __name__ == '__main__' : douban = DoubanSpider() douban.run() print("complete" )
2019-02-25 更新
通用案例之糗事百科段子 作为一名段子手- -不应该只会刷段子,还要学会爬取。。 其中用到了 lxml模块和xpath相关内容,爬取到的信息不止有文字内容,还有图片以及作者名字和性别,但此处只在txt文档里放了段子内容。 效果图如下:
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import requestsfrom lxml import htmlclass QuibaiSpdier : def __init__ (self) : self.url_temp = "https://www.qiushibaike.com/hot/page/{}/" self.headers = { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36" } def get_url_list (self) : return [self.url_temp.format(i) for i in range(1 , 14 )] def parse_url (self, url) : response = requests.get(url, headers=self.headers) return response.content.decode() def get_content_list (self, html_str) : html_elements = html.etree.HTML(html_str) div_list = html_elements.xpath("//div[@id='content-left']/div" ) content_list = [] for div in div_list: item = {} item["content" ] = div.xpath(".//div[@class='content']/span/text()" ) item["author_gender" ] = div.xpath(".//div[contains(@class,'articleGender')]/@class" ) item["author_gender" ] = item["author_gender" ][0 ].split(" " )[-1 ].replace("Icon" , "" ) if len( item["author_gender" ]) > 0 else None item["content_img" ] = div.xpath(".//div[@class='thumb']/a/img/@src" ) item["content_img" ] = "https:" + item["content_img" ][0 ] if len(item["content_img" ]) > 0 else None item["author_img" ] = div.xpath(".//div[@class='author clearfix']//img/@src" ) item["author_img" ] = "https:" + item["author_img" ][0 ] if len(item["author_img" ]) > 0 else None item["stats_vote" ] = div.xpath(".//span[@class='stats-vote']/i/text()" ) item["stats_vote" ] = item["stats_vote" ][0 ] if len(item["stats_vote" ]) > 0 else None content_list.append(item) return content_list def save_content_list (self, content_list) : with open("qiubai.txt" , "a" , encoding='utf-8' ) as f: for content in content_list: f.write("\n" .join(content["content" ]) + "点赞数:" + content["stats_vote" ]) f.write("\n" ) def run (self) : url_list = self.get_url_list() for url in url_list: html_str = self.parse_url(url) content_list = self.get_content_list(html_str) self.save_content_list(content_list) if __name__ == '__main__' : qiubai = QuibaiSpdier() qiubai.run()