爬虫的分类:
通用:
聚焦:数据解析
增量式:监测
http:客户端和服务器端进行数据交互的形式
证书密钥加密:
什么是证书?
证书种包含的是经过数字签名的公钥
反爬:
robots
UA伪装
请求载体的身份标识
在headers种应用一个字典(请求头信息:UA)
动态加载的数据
如何处理动态请求参数:
封装到一个字典中,字典需要作用到data或者params
- 编码的流程
- 指定url
- 发起请求
- 获取响应数据
- 数据解析
- 持久化存储
- 数据解析的作用
- 用于获取页面中局部的页面源码数据
- 如何实现数据解析
- 正则
- bs4(独有)
- xpath(最为通用)
- pyquery
- 数据解析的通用原理是什么?
- 标签定位
- 将标签中间存储的文本数据或者其属性值进行捕获
正则解析
- 需求:爬取糗事百科中的图片数据
- 确认了页面中没有动态加载数据的存在
#爬取糗事百科
# re正则匹配
import requests
import re
import os
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
}
dirname="./qiutu"
# 创建文件夹
if not os.path.exists(dirname):
os.mkdir(dirname)
url="https://www.qiushibaike.com/imgrank/page/%d/"
for page in range(1,3):
print("开始下载第{}页图片".format(page))
#指定新的url
new_url=format(url%page)
#获得源码文本信息
page_text=requests.get(url=new_url,headers=headers).text
ex='<div class="thumb">.*?<img src="(.*?)" alt=.*?</div>'
# 获取图片地址 注意re.S
img_src_list=re.findall(ex,page_text,re.S)
for img_src in img_src_list:
new_img_src='https:'+img_src
img_name=img_src.split("/")[-1]
img_path=dirname+"/"+img_name
img_text=requests.get(url=new_img_src,headers=headers).content
with open(img_path,"wb") as f:
f.write(img_text)
print(img_name,"下载完毕!")
# 方法2:
# from urllib import request
# request.urlretrieve(new_img_src,img_path)
# <div class="thumb">
# <a href="/article/121911520" target="_blank">
# <img src="//pic.qiushibaike.com/system/pictures/12191/121911520/medium/S4TSN79VOC3G0R83.jpg" alt="糗事#121911520" class="illustration" width="100%" height="auto">
# </a>
# </div
bs4解析
- 环境的安装:
- pip install bs4
- pip install lxml
- bs4解析原理
- 实例化一个BeautifulSoup的对象,且将即将被解析的页面源码加载到该对象中
- 使用该对象中的属性或者方法进行标签定位和数据提取
- BeautifulSoup对象的实例化方式:lxml就是一款解析器
- BeautifulSoup(fp,'lxml'):将本地存储的html文档加载到该对象中
- BeautifulSoup(page_text,'lxml'):将互联网上获取的html源码加载到该对象中
from bs4 import BeautifulSoup import lxml fp=open("./bs.html",'r',encoding="utf-8") soup=BeautifulSoup(fp,"lxml") soup
# 标签定位 # soup.tagName:返回的就是页面中第一次出现的tagName标签(返回的是一个单数) # soup.title # soup.div # find函数的用法:属性定位 # soup.find('tagName',attrName='value') # 注意:返回的是单数 soup.find('div',class_="song") # soup.find_all('tagName'):定位所有的tagName的标签 # soup.find_all('tagName',attrName='value'):属性定位 # 注意:返回值是列表 # soup.find_all("div") # soup.find_all("div",class_="song") # select('选择器'):根据选择器进行标签定位且返回的是复数(列表) # 类选择器,id选择器,标签选择器,层级选择器 # 层级选择器:>表示一个层级,空格表示多个层级 # soup.select(".song > p")[0] # soup.select(".tang > ul > li") # soup.select(".tang li") # 取数据(属性值和标签中存储的文本数据) # text 和 string 的区别: # string 获取的是标签中直系的文本内容 # text 获取标签中所有的文本内容 # soup.p.string # soup.p.text # 取属性: # tag['attrName'] # soup.select("div > a")[0]["href"] # for a in soup.select(".tang > ul > li > a"): # print(a["href"])
使用流程: - 导包:from bs4 import BeautifulSoup - 使用方式:可以将一个html文档,转化为BeautifulSoup对象,然后通过对象的方法或者属性去查找指定的节点内容 (1)转化本地文件: - soup = BeautifulSoup(open('本地文件'), 'lxml') (2)转化网络文件: - soup = BeautifulSoup('字符串类型或者字节类型', 'lxml') (3)打印soup对象显示内容为html文件中的内容 基础巩固: (1)根据标签名查找 - soup.a 只能找到第一个符合要求的标签 - soup.div 只能找到第一个div标签 (2)获取属性 - soup.a.attrs 获取a所有的属性和属性值,返回一个字典 - soup.a.attrs['href'] 获取href属性 - soup.a['href'] 也可简写为这种形式 (3)获取内容 - soup.a.string # string获取的是标签中直系的文本内容 - soup.a.text # text获取的是当前标签下所有文本内容,包括子标签 - soup.a.get_text() 【注意】如果标签还有标签,那么string获取到的结果为None,而其它两个,可以获取文本内容 (4)find:找到第一个符合要求的标签 - soup.find('a') 找到第一个符合要求的 - soup.find('a', title="xxx") 获取title=xxx的第一个标签 - soup.find('a', alt="xxx") h获取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)根据选择器选择指定的内容 - soup.select('#tang') - 常见的选择器:标签选择器(a)、类选择器(.)、id选择器(#)、层级选择器 - 层级选择器: select(".tang li") #class=tang标签下面的所有li标签,包含所有层级 div > p > a > .lala # 只能是下面一级 【注意】select选择器返回永远是列表,需要通过下标提取指定的对象 总结:层级选择器定位返回总是一个复数(列表) 类选择器,id选择器,标签选择器,层级选择器 层级选择器:>表示一个层级,空格表示多个层级
爬取三国演义小说
url = 'http://www.shicimingju.com/book/sanguoyanyi.html' page_text = requests.get(url=url,headers=headers).text #使用bs4进行数据解析(章节标题&内容) soup = BeautifulSoup(page_text,'lxml') a_list = soup.select('.book-mulu > ul > li > a') fp = open('sanguo.txt','w',encoding='utf-8') for a in a_list: title = a.string detail_url = 'http://www.shicimingju.com'+a['href'] #对详情页的url发起请求解析出章节内容 detail_page_text = requests.get(url=detail_url,headers=headers).text detail_soup = BeautifulSoup(detail_page_text,'lxml') content = detail_soup.find('div',class_='chapter_content').text fp.write(title+':'+content+' ') print(title,'保存成功!!!') fp.close()
xpath
xpath解析
- 环境安装:pip install lxml
- 解析原理:
- 实例化一个etree类型的对象,且将即将被解析的页面源码数据加载到该对象中
- 调用该对象中的xpath方法结合着不同的xpath表达式进行标签定位和数据提取
- 实例化对象:
- etree.parse(fileName)
- etree.HTML(page_text)
from lxml import etree tree=etree.parse('bs.html') # 属性定位 # tree.xpath('//div[@class="song"]') # 索引定位 # 索引值是从1开始 # 在xpath表达式中非最左侧的/和//的区别? # /表示一个层级 # //表示多个层级 # tree.xpath("//div[@class='song']//text()") # 取文本 # /text():获取的是标签下直系的文本数据 # //text():获取的是标签下所有的文本数据 # tree.xpath("//div[@class='song']/p[1]/text()") # tree.xpath("//div[@class='song']//a[1]/span/text()") # 取属性 # tree.xpath("//div[@class='song']//img/@src")
基于标签定位: tree.xpath('/html/head/meta') tree.xpath('//meta') xpath表达式中最左侧的/和//的区别是什么? /表示我们必须从根标签进行定位 //表示我们可以从任意位置标签定位 属性定位: #找到class属性值为song的div标签 //div[@class="song"] 层级&索引定位: #找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a //div[@class="tang"]/ul/li[2]/a tree.xpath('//div[@class="tang"]/ul/li[3]') tree.xpath('//div[@class="tang"]//li[3]') # 与上一条属性索取为相同结果 在xpath表达式中非最左侧的/和//的区别? /表示一个层级 //表示多个层级 逻辑运算: #找到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
# 爬取boss中的岗位信息(岗位名称,薪资,公司名称,岗位描述) import requests from lxml import etree headers={ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } url="https://www.zhipin.com/c101010100/?query=python后端&page=%d" fp=open("boss.txt","w",encoding="utf-8") for page in range(1,4): new_url=format(url%page) page_text=requests.get(url=new_url,headers=headers).text tree=etree.HTML(page_text) # 获取工作列表 li_list=tree.xpath("//div[@class='job-box']/div[@class='job-list']/ul/li") for li in li_list: job_title=li.xpath("./div//h3[@class='name']/a/div[1]/text()")[0] salary=li.xpath("./div//h3[@class='name']/a/span/text()")[0] company=li.xpath("./div/div[2]/div/h3/a/text()")[0] # 详细信息网页 detail_url='https://www.zhipin.com' + li.xpath('./div/div[1]/h3/a/@href')[0] detail_text=requests.get(url=detail_url,headers=headers).text detail_tree=etree.HTML(detail_text) desc=detail_tree.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div/text()') desc=''.join(desc) fp.write(job_title+":"+" "+salary+" "+company+" "+desc) fp.close()
# 处理中文乱码问题 # url = 'http://pic.netbian.com/4kmeinv/index_%d.html' import requests from lxml import etree headers={ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } fp=open('meinv.txt','w',encoding='utf-8') for page in range(1,4): if page==1: url="http://pic.netbian.com/4kmeinv/" else: url="http://pic.netbian.com/4kmeinv/index_{}.html".format(page) response=requests.get(url=url,headers=headers) # 解决中文乱码方式1 耗资源 不推荐 # response.encoding='gbk' page_text=response.text tree=etree.HTML(page_text) li_list=tree.xpath('//*[@id="main"]/div[3]/ul/li') for li in li_list: img_name=li.xpath("./a/b/text()")[0] # 解决中文乱码方式2 img_name=img_name.encode("iso-8859-1").decode("gbk") img_url='http://pic.netbian.com' + li.xpath('./a/img/@src')[0] fp.write(img_name+":"+img_url+" ") fp.close()
# 增强xpath表达式的通用性 ****采用管道符 # url="https://www.aqistudy.cn/historydata/" # 获取热门城市与普通城市的城市名 import requests from lxml import etree headers={ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } url="https://www.aqistudy.cn/historydata/" response=requests.get(url=url,headers=headers) page_text=response.text tree=etree.HTML(page_text) # hot_city=tree.xpath('//div[@class="hot"]//div[@class="bottom"]/ul/li/a/text()') # print(hot_city) # all_city=tree.xpath('//div[@class="all"]//div[@class="bottom"]/ul/div[2]/li/a/text()') # print(all_city) # cities=tree.xpath('//div[@class="hot"]//div[@class="bottom"]/ul/li/a/text() | //div[@class="all"]//div[@class="bottom"]/ul/div[2]/li/a/text()') # print(cities) # cities=tree.xpath('//div[@class="all"]//div[@class="bottom"]/ul/div[2]/li/a/text() | //div[@class="hot"]//div[@class="bottom"]/ul/li/a/text()') # print(cities)
举例
# 糗事百科的段子内容和作者(xpath的管道符)名称进行爬取,然后存储到mysql中or文本 import requests from lxml import etree import os headers={ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } dirName='./homework' if not os.path.exists(dirName): os.mkdir(dirName) filepath=os.path.join(dirName,"qiubai.txt") fp=open(filepath,'w',encoding="utf-8") # 指定url url='https://www.qiushibaike.com/text/page/%d/' for page in range(1,10): print("开始下载第{}页".format(page)) new_url=format(url%page) # 发送请求 获取数据 response=requests.get(url=new_url,headers=headers) page_text=response.text tree=etree.HTML(page_text) info_list=tree.xpath('//div[@id="content-left"]/div') for info in info_list: # 管道符号获取所有用户 user_name=info.xpath("./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()")[0] content=info.xpath("./a[1]/div/span/text()") content=''.join(content) qiubai=user_name.strip()+":"+content.strip()+" " fp.write(qiubai) fp.close() print("下载完毕!")
# http://sc.chinaz.com/jianli/free.html爬取简历模板 import requests from lxml import etree import os import time import random headers={ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } dirName='./homework' if not os.path.exists(dirName): os.mkdir(dirName) for page in range(1,3): if page==1: url='http://sc.chinaz.com/jianli/free.html' else: url='http://sc.chinaz.com/jianli/free_{}.html'.format(page) page_text=requests.get(url=url,headers=headers).text tree=etree.HTML(page_text) a_list=tree.xpath('//div[@id="container"]/div/a/@href') # print(a_list) for a in a_list: detail_text=requests.get(url=a,headers=headers).text detail_tree=etree.HTML(detail_text) # 下载路径 a_detail=detail_tree.xpath('//*[@id="down"]/div[2]/ul/li[1]/a/@href')[0] # print(a_detail) filename=detail_tree.xpath('//div[@class="ppt_left fl"]//h1//text()')[0] # 识别中文 filename1 = filename.encode("iso-8859-1").decode("utf-8")+'.rar' print(filename1) response=requests.get(url=a_detail,headers=headers) file_content=response.content filepath=os.path.join(dirName,filename1) print(filepath) with open(filepath,'wb') as f: f.write(file_content) time.sleep(random.randint(1,3)) print("ok1111111111111") print("ok")
开线程池 # http://sc.chinaz.com/jianli/free.html爬取简历模板 from concurrent.futures import ThreadPoolExecutor import requests from lxml import etree import os import time import random print("start") start=time.time() headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } dirName = './homework' if not os.path.exists(dirName): os.mkdir(dirName) url_dic={} for page in range(1, 3): if page == 1: url = 'http://sc.chinaz.com/jianli/free.html' else: url = 'http://sc.chinaz.com/jianli/free_{}.html'.format(page) page_text = requests.get(url=url, headers=headers).text tree = etree.HTML(page_text) a_list = tree.xpath('//div[@id="container"]/div/a/@href') # print(a_list) for a in a_list: detail_text = requests.get(url=a, headers=headers).text detail_tree = etree.HTML(detail_text) # 下载路径 a_detail = detail_tree.xpath('//*[@id="down"]/div[2]/ul/li[1]/a/@href')[0] # print(a_detail) filename = detail_tree.xpath('//div[@class="ppt_left fl"]//h1//text()')[0] # 识别中文 filename1 = filename.encode("iso-8859-1").decode("utf-8") + '.rar' url_dic[filename1]=a_detail # response = requests.get(url=a_detail, headers=headers) # file_content = response.content # # filepath = os.path.join(dirName, filename1) # # print(filepath) # with open(filepath, 'wb') as f: # f.write(file_content) # # time.sleep(random.randint(1, 3)) print(url_dic) def get_html(name,url): res=requests.get(url,headers=headers) return {"name":name,"content":res.content} def parser_page(ret_obj): dic=ret_obj.result() with open(dic['name'], 'wb') as f: f.write(dic['content']) t=ThreadPoolExecutor(30) for name in url_dic: task=t.submit(get_html,name,url_dic[name]) task.add_done_callback(parser_page) print("ok",time.time()-start)