爬虫-requests模块
requests模块:python中原生的一款基于网络请求的模块,功能非常强大,简单便捷,效率极高。
作用:模拟浏览器发请求。
requests模块的使用流程
- 1、指定url
- 2、发起请求
- UA伪装
- 请求参数的处理
- 3、获取响应数据
- 4、持久化存储
requrest模块基本知识点
import requests import json url = "www.baidu.com" headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' } data = { 'on': 'true', 'page': page, 'pageSize': '15', 'productName':'', 'conditionType': '1', 'applyname':'', 'applysn':'', } response = requests.get(url=rul, params=params, headers=headers).text # get: 发起get请求 # url:是被访问的页面url # params:get请求时需要的参数,按字典形式数据结构 # headers: UA伪装需要的参数,字典形式 # text: 获取请求响应页面的文本数据 response = requests.post(url=rul, data=data, headers=headers).text # post: 发起post请求 # data:post请求时需要的参数,按字典形式数据结构,同get请求时的params data_json = requests.get(url=rul, params=params, headers=headers).json() json.dump(data_json,fp=fp,ensure_ascii=False) # 页面响应的数据的是json数据类型,此时只能用json()方法接收 # json.dump: json响应数据持久化存储 # data_json: 可能是字典或列表等数据类型的对象 # ensure_ascii=False:对于中文不使用ascii编码 img_data = requests.get(url=rul, params=params, headers=headers).content with open('./qiutu.jpg','wb') as fp: fp.write(img_data) # content: 对于文件对象的响应数据使用content接收 # 文件对象直接持久化存储
实战示例
# 需求:国家药品监督局数据爬取 import requests import json headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' } id_list = [] # 存储企业的id all_data_list = [] # 存储所有的企业详情数据 #批量获取不同企业的id值 url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList' #参数的封装 for page in range(1,6): page = str(page) data = { 'on': 'true', 'page': page, 'pageSize': '15', 'productName':'', 'conditionType': '1', 'applyname':'', 'applysn':'', } json_ids = requests.post(url=url,headers=headers,data=data).json() for dic in json_ids['list']: id_list.append(dic['ID']) #获取企业详情数据 post_url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById' for id in id_list: data = { 'id':id } detail_json = requests.post(url=post_url,headers=headers,data=data).json() # print(detail_json,'-------------ending-----------') all_data_list.append(detail_json) #持久化存储all_data_list fp = open('./allData.json','w',encoding='utf-8') json.dump(all_data_list,fp=fp,ensure_ascii=False) print('over!!!')
3.requests数据解析
回顾requests模块实现数据爬取的流程
- 1、指定url
- 2、发起请求
- UA伪装
- 请求参数的处理
- 3、获取响应数据
- 4、持久化存储
其实,在上述流程中还需要较为重要的一步,就是在持久化存储之前需要进行指定数据解析。因为大多数情况下的需求,我们都会指定去使用聚焦爬虫,也就是爬取页面中指定部分的数据值,而不是整个页面的数据。因此,本次课程中会给大家详细介绍讲解三种聚焦爬虫中的数据解析方式。至此,我们的数据爬取的流程可以修改为:
- 指定url
- 发起请求
- 获取响应数据
- 数据解析
- 持久化存储
数据解析方式
- 正则表达式
- bs4解析
- xpath解析
数据解析原理
- 解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储 1.进行指定标签的定位 2.标签或者标签对应的属性中存储的数据值进行提取(解析)
正则解析
常用正则表达式回顾
# 需求:解析图片标签src属性中的url <div class="thumb"> <a href="/article/121721100" target="_blank"> <img src="//pic.qiushibaike.com/system/pictures/12172/121721100/medium/DNXDX9TZ8SDU6OK2.jpg" alt="指引我有前进的方向"> </a> </div> ex = '<div class="thumb">.*?<img src="(.*?)" alt.*?</div>'
实战示例
import requests import re import os if __name__ == "__main__": url = 'https://www.qiushibaike.com/pic/%s/' headers={ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', } #指定起始也结束页码 page_start = int(input('enter start page:')) page_end = int(input('enter end page:')) #创建文件夹 if not os.path.exists('images'): os.mkdir('images') #循环解析且下载指定页码中的图片数据 for page in range(page_start,page_end+1): print('正在下载第%d页图片'%page) new_url = format(url % page) response = requests.get(url=new_url,headers=headers) #解析response中的图片链接 e = ' .*?.*? ' pa = re.compile(e,re.S) image_urls = pa.findall(response.text) #循环下载该页码下所有的图片数据 for image_url in image_urls: image_url = 'https:' + image_url image_name = image_url.split('/')[-1] image_path = 'images/'+image_name image_data = requests.get(url=image_url,headers=headers).content with open(image_path,'wb') as fp: fp.write(image_data)
bs4解析
数据解析的原理
- 1.标签定位
? - 2.提取标签、标签属性中存储的数据值
bs4数据解析的原理
- 1.实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中 - 2.通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取
环境安装
- pip install bs4
- pip install lxml
基本使用
使用流程: - 导包:from bs4 import BeautifulSoup - 使用方式:可以将一个html文档,转化为BeautifulSoup对象,然后通过对象的方法或者属性去查找指定的节点内容 (1)转化本地文件: - soup = BeautifulSoup(open('本地文件'), 'lxml') (2)转化网络文件: - soup = BeautifulSoup('字符串类型或者字节类型', 'lxml') (3)打印soup对象显示内容为html文件中的内容 基础巩固: (1)根据标签名查找 - soup.a 只能找到第一个符合要求的标签 (2)获取属性 - soup.a.attrs 获取a所有的属性和属性值,返回一个字典 - soup.a.attrs['href'] 获取href属性 - soup.a['href'] 也可简写为这种形式 (3)获取内容 - soup.a.string - soup.a.text - soup.a.get_text() 【注意】 - string仅获取直系下的文本内容 - text 和get_text() 获取该节点下所有层级内的文本内容 (4)find:找到第一个符合要求的标签 - soup.find('a') 找到第一个符合要求的 - soup.find('a', title="xxx") - soup.find('a', alt="xxx") - soup.find('a', class_="xxx") - soup.find('a', id="xxx") (5)find_all:找到所有符合要求的标签 - soup.find_all('a') - soup.find_all(['a','b']) 找到所有的a和b标签 - soup.find_all('a', limit=2) 限制前两个 (6)根据选择器选择指定的内容 select: - soup.select('#feng') - 常见的选择器:标签选择器(a)、类选择器(.)、id选择器(#)、层级选择器 - 层级选择器: div .dudu #lala .meme .xixi 下面好多级 div > p > a > .lala 只能是下面一级 【注意】select选择器返回永远是列表,需要通过下标提取指定的对象
项目实战
# 需求:使用bs4实现将诗词名句网站中三国演义小说的每一章的内容爬去到本地磁盘进行存储 # http://www.shicimingju.com/book/sanguoyanyi.html import requests from bs4 import BeautifulSoup headers={ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', } def parse_content(url): #获取标题正文页数据 page_text = requests.get(url,headers=headers).text soup = BeautifulSoup(page_text,'lxml') #解析获得标签 ele = soup.find('div',class_='chapter_content') content = ele.text #获取标签中的数据值 return content if __name__ == "__main__": url = 'http://www.shicimingju.com/book/sanguoyanyi.html' reponse = requests.get(url=url,headers=headers) page_text = reponse.text #创建soup对象 soup = BeautifulSoup(page_text,'lxml') #解析数据 a_eles = soup.select('.book-mulu > ul > li > a') print(a_eles) cap = 1 for ele in a_eles: print('开始下载第%d章节'%cap) cap+=1 title = ele.string content_url = 'http://www.shicimingju.com'+ele['href'] content = parse_content(content_url) with open('./sanguo.txt','w') as fp: fp.write(title+":"+content+'\n\n\n\n\n') print('结束下载第%d章节'%cap)
xpath解析
xpath解析是我们在爬虫中最常用也是最通用的一种数据解析方式,由于其高效且简介的解析方式受到了广大程序员的喜爱。在后期学习scrapy框架期间,也会再次使用到xpath解析。
解析原理
- 使用通用爬虫爬取网页数据
- 实例化etree对象,且将页面数据加载到该对象中
- 使用xpath函数结合xpath表达式进行标签定位和指定数据提取
etree对象实例化
- 本地文件:tree = etree.parse(文件名) tree.xpath("xpath表达式") - 网络数据:tree = etree.HTML(网页内容字符串) tree.xpath("xpath表达式")
常用xpath表达式
- /: 表示的是从根节点开始定位。表示的是一个层级。 - //: 表示的是多个层级。可以表示从任意位置开始定位。 - ./: 表示从当前节点开始定位。 属性定位: #找到class属性值为song的div标签 //div[@class="song"] 层级&索引定位: #找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a //div[@class="tang"]/ul/li[2]/a 逻辑运算: #找到href属性值为空且class属性值为du的a标签 //a[@href="" and @class="du"] 模糊匹配: //div[contains(@class, "ng")] //div[starts-with(@class, "ta")] 取文本: # /表示获取某个标签下的文本内容 # //表示获取某个标签下的文本内容和所有子标签下的文本内容 //div[@class="song"]/p[1]/text() //div[@class="tang"]//text() 取属性: //div[@class="tang"]//li[2]/a/@href /@attrName ==>img/src
偷懒方式
- 使用谷歌浏览器开发模式直接复制xpath
项目实战
# 需求: 爬取58二手房信息 # 知识点: 数据解析 xpath import requests from lxml import etree url = "https://hz.58.com/ershoufang/" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36" } # 获取页面请求文本数据 page_text = requests.get(url=url, headers=headers).text # 实例化etree对象 tree = etree.HTML(page_text) li_list = tree.xpath("//ul[@class='house-list-wrap']/li") fp = open("58二手房.text", "w", encoding="utf-8") for li in li_list: # 标题 title = li.xpath('./div[@class="list-info"]/h2/a/text()')[0] describe_info_list = li.xpath('./div[@class="list-info"]/p[1]/span/text()') # 详细信息 describe_info = '-'.join(describe_info_list) # 地址信息 addr_info_list = li.xpath('./div[@class="list-info"]/p[2]/span/a/text()') addr_info = '-'.join(addr_info_list) # 价格 total_price_list = li.xpath('./div[@class="price"]/p[@class="sum"]//text()') total_price = ''.join(total_price_list) unit_price = li.xpath('./div[@class="price"]/p[@class="unit"]//text()')[0] fp.write(f"{title}\n\t{describe_info}\n\t{addr_info}\n\t{total_price}-{unit_price}\n\n") print("下载...")
实战2
问题:处理中文乱码问题
解决方式1:相应数据编码成utf-8
response = requests.get(url=url, headers=headers) response.encoding = "utf-8" page_text =response.text
解决方式2:
img_name = li.xpath('./a/img/@alt')[0].encode('iso-8859-1').decode('gbk')
# 需求 4k图片爬取 # 知识点:xpath 图片 中文编码 import requests from lxml import etree import os if not os.path.exists("./files"): os.mkdir("files") url = 'http://pic.netbian.com/4kdongwu/' headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36" } # 中文乱码 处理方式1 # response = requests.get(url=url, headers=headers) # response.encoding = "utf-8" # page_text =response.text page_text = requests.get(url=url, headers=headers).text # 获取页面对象 tree = etree.HTML(page_text) li_list = tree.xpath('//div[@class="slist"]//li') for li in li_list: img_url = 'http://pic.netbian.com' + li.xpath('./a/img/@src')[0] img_name = li.xpath('./a/img/@alt')[0].encode('iso-8859-1').decode('gbk') img_data = requests.get(url=img_url, headers=headers).content filePath = "files/"+img_name+".jpg" with open(filePath, "wb") as fp: fp.write(img_data) print(img_name, "下载完毕")
模拟登陆
# 需求 模拟登陆古诗文网 import requests from lxml import etree from CodeClass import YDMHttp, getCodeText url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx' headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36" } page_text = requests.get(url=url, headers=headers).text tree = etree.HTML(page_text) valid_img_url = "https://so.gushiwen.org" + tree.xpath('//*[@id="imgCode"]/@src')[0] valid_img_data = requests.get(url=valid_img_url, headers=headers).content with open('code.jpg', "wb") as fp: fp.write(valid_img_data) code_text = getCodeText("code.jpg", 1004) __VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0] __VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0] login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx' data = { '__VIEWSTATE': __VIEWSTATE, '__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR, 'from': ' http://so.gushiwen.org/user/collect.aspx', 'email': '15501257708', 'pwd': '12345678', 'code': code_text, 'denglu': ' 登录', } response = requests.post(url=login_url, data=data, headers=headers) print("状态码:", response.status_code) after_login_text = response.text with open("古诗文登陆页面.html", "w", encoding='utf-8') as fp: fp.write(after_login_text)
cookie处理
- 在浏览网站的过程中,我们经常会遇到需要登录的情况,有些页面只有登录之后才可以访问,而且登录之后可以连续访问很多次网站,但是有时候过一段时间就需要重新登录。还有一些网站,在打开浏览器时就自动登录了,而且很长时间都不会失效,这种情况又是为什么?其实这里面就涉及到了会话和cookie的相关知识,本节就来揭开他们的神秘面纱。
- 当用浏览器登录服务器时,浏览器会保存服务器发送的cookie,所以可以完成会话。但是当我们用代码模拟登陆时,没有相关cookie,就会不好使无法继续会话。
解决办法有两种:
手动添加cookie(不推荐)
headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', 'Cookie':'xxxxxxxxx' }
使用session
1.可以进行请求的发送。
2.如果请求过程中产生了cookie,则该cookie会被自动存储/携带在该session对象中。
创建一个session对象:session = requests.Session()
session对象进行模拟登录post请求的发送(cookie就会被存储在session中)
? ession对象对个人主页对应的get请求进行发送(携带了cookie)
# 需求:登录人人网后爬取个人信息 from CodeClass import YDMHttp,getCodeText import requests from lxml import etree session = requests.Session() #1.对验证码图片进行捕获和识别 headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' } url = 'http://www.renren.com/SysHome.do' page_text = requests.get(url=url,headers=headers).text tree = etree.HTML(page_text) code_img_src = tree.xpath('//*[@id="verifyPic_login"]/@src')[0] code_img_data = requests.get(url=code_img_src,headers=headers).content with open('./code.jpg','wb') as fp: fp.write(code_img_data) #使用云打码提供的示例代码对验证码图片进行识别 result = getCodeText('code.jpg',1000) #post请求的发送(模拟登录) login_url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=2019431046983' data = { 'email': '', 'icode': result, 'origURL': 'http://www.renren.com/home', 'domain': 'renren.com', 'key_id': '1', 'captcha_type': 'web_login', 'password': '06768edabba49f5f6b762240b311ae5bfa4bcce70627231dd1f08b9c7c6f4375', 'rkey': '3d1f9abdaae1f018a49d38069fe743c8', 'f':'', } #使用session进行post请求的发送 response = session.post(url=login_url,headers=headers,data=data) print(response.status_code) #爬取当前用户的个人主页对应的页面数据 detail_url = 'http://www.renren.com/289676607/profile' #手动cookie处理 # headers = { # 'Cookie':'xxxx' # } #使用携带cookie的session进行get请求的发送 detail_page_text = session.get(url=detail_url,headers=headers).text with open('bobo.html','w',encoding='utf-8') as fp: fp.write(detail_page_text)
代理IP
我们在做爬虫的过程中经常会遇到这样的情况,最初爬虫正常运行,正常抓取数据,一切看起来都是那么美好,然而一杯茶的功夫可能就会出现错误,比如403,这时打开网页一看,可能会看到“您的IP访问频率太高”这样的提示。出现这种现象的原因是网站采取了一些反爬措施。比如,服务器会检测某个IP在单位时间内请求的次数,如果超过了某个阈值,就会直接拒绝服务,返回一些错误信息,这种情况可以称为封IP。
既然服务器检测的是某个IP单位时间的请求次数,那么借助某种方式来伪装我们的IP,让服务器识别不出是由我们本机发起的请求,不就可以成功防止封IP了吗?一种有效的方式就是使用代理。
什么是代理
- 代理实际上指的就是代理服务器,它的功能就是代理网络用户去取得网络信息。形象的说,它是网络信息的中转站。在我们正常请求一个网站时,是发送了请求给Web服务器,Web服务器把响应传回我们。如果设置了代理服务器,实际上就是在本机和服务器之间搭建了一个桥梁,此时本机不是直接向Web服务器发起请求,而是向代理服务器发出请求,请求会发送给代理服务器,然后代理服务器再发送给Web服务器,接着由代理服务器再把Web服务器返回的响应转发给本机。这样我们同样可以正常访问网页,但这个过程中Web服务器识别出的真实IP就不再是我们本机的IP了,就成功实现了IP伪装,这就是代理的基本原理。
代理的作用
- 突破自身IP访问的限制,访问一些平时不能访问的站点
- 隐藏真实IP,免受攻击,防止自身IP被封锁
如果我们使用requests模块相关操作应用了代理,则请求到该页面中显示的ip信息就是代理IP相关信息了。
我们可以使用requests模块请求方法的 proxies参数 处理代理IP。
proxy = {"http": "112.115.57.20:3128"} response = requests.get(url=url,headers=header,proxies=proxy)
实战
import requests import random if __name__ == "__main__": #不同浏览器的UA header_list = [ # 遨游 {"user-agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)"}, # 火狐 {"user-agent": "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"}, # 谷歌 { "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"} ] #不同的代理IP proxy_list = [ {"http": "112.115.57.20:3128"}, {'http': '121.41.171.223:3128'} ] #随机获取UA和代理IP header = random.choice(header_list) proxy = random.choice(proxy_list) url = 'http://www.baidu.com/s?ie=UTF-8&wd=ip' #参数3:设置代理 response = requests.get(url=url,headers=header,proxies=proxy) response.encoding = 'utf-8' with open('daili.html', 'wb') as fp: fp.write(response.content)