通用爬虫
抓取系统重要组成部分。抓取的是一整张页面数据
聚焦爬虫
是建立在通用爬虫基础之上。抓取的是页面中特定的局部内容
增量式爬虫
检测网站中数据更新的情况。只会抓取网站中最新更新出来的数据
反爬机制
门户网站可以通过制定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取
反反爬机制
爬虫程序可以通过制定相关的策略或者技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站中相关的数据
robots.txt协议
君子协议。规定了网站中哪些数据可以被爬虫爬取,哪些数据不可以被爬取
比如查看bilibili的robots.txt
输入:www.bilibili.com/roobots.txt
概念:服务器与客户端进行数据交互的一种形式
常用请求头信息:
User-Agent:请求载体的身份标识
Connection:请求完毕后,是断开连接还是保持连接(close/keep-alive)
常用响应头信息:
Content-Type:服务器响应回客户端的数据类型
https协议:
安全的http协议
加密方式:
对称秘钥加密:客户端对要传输的数据自定义加密,然后把加密后的数据以及加密的方式一并传给服务器
非对称秘钥加密:服务器端将加密方式(公钥)传递给客户端,客户端对要发送的数据使用传递过来的加密方式加密以后,再发送给服务器。服务器可以通过对应的解密方法(私钥)来破解客户端传过来的数据
证书秘钥加密:客户端将加密方式(公钥)传递给证书认证机构,证书认证机构对公钥审核通过以后就会给公钥签名(数字签名防伪),然后将数字签名和公钥一并发送给客户端。客户端如果见到了数字签名,就会信任这个公钥。
基于网络请求,有两个模块:urllib、requests
urllib比较古老,不再赘述
什么是requests模块?
python中原生的一款基于网络请求的模块,功能非常强大,简单便捷,效率极高
作用:模拟浏览器发送请求
如何使用requests模块?
①指定url
②发起请求
③获取响应数据
④持久化存储
环境安装:
pip install requests
实战编码:
需求:爬取搜狗首页的页面数据
import requests
if __name__ == '__main__':
# 指定url
url="https://www.sogou.com/"
# 发送请求,获得响应对象
response=requests.get(url)
# 获取响应数据 response.text返回的是字符串形式的响应数据
page_text=response.text
print(page_text)
# 持久化存储
with open('./sogou.html','w',encoding='utf-8') as fp:
fp.write(page_text)
print("爬取数据结束!!!")
UA伪装:
UA:User-Agent
UA检测:门户网站的服务器会检测对应请求的载体身份标识,如果监测到请求的载体身份标识是某一款浏览器,就说明该请求是一个正常的请求。但是,如果检测到请求的载体身份标识不是基于某一款浏览器的,则表示该请求为不正常的请求(爬虫),则服务器端很有可能拒绝该次请求
UA伪装:让爬虫对应的请求载体身份标识为某一款浏览器
import requests
if __name__=='__main__':
#UA伪装
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
url='https://www.sogou.com/web'
# 处理url携带的参数封装到字典中
kw=input('输入名词:')
param={
'query':kw
}
response=requests.get(url,param,headers=headers)
fileName=kw+'.html'
with open(fileName,'w',encoding="utf-8") as files:
files.write(response.text)
print("爬虫结束")
import requests
import json
url='https://fanyi.baidu.com/sug'
kw=input("请输入一个单词:")
# post请求参数处理
data={
"kw":kw
}
# UA伪装
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
response=requests.post(url,data,headers=headers)
# 获取响应数据:json()方法返回的是一个obj(如果确认响应数据是json类型,才可以使用json())
dic_obj=response.json()
print(dic_obj)
# 持久化存储
fp=open(kw+".json","w",encoding="utf-8")
# 拿到的json有中文,不能用ascii编码,所以设置ensure_ascii=False
json.dump(dic_obj,fp=fp,ensure_ascii=False)
print("爬取百度翻译页面完毕!")
执行结果:
import requests
import json
url='https://movie.douban.com/j/chart/top_list'
params={
'type':'1',
'interval_id':'100:90',
'action':'',
'start':'0',
'limit':'20'
}
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0'
}
response=requests.get(url,params,headers=headers)
list_data=response.json()
print(list_data)
fp=open('./douban.json','w',encoding="utf-8")
json.dump(list_data,fp,ensure_ascii=False)
print("over!")
import requests
url="http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword"
data={
"cname":"",
"pid":"",
"keyword":"南昌",
"pageIndex":"1",
"pageSize":"59"
}
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
response=requests.post(url,data,headers=headers)
page_text=response.text
with open("./kfc.txt","w",encoding="utf-8") as files:
files.write(page_text)
print("爬虫完毕!!")
需求:爬取国家药品监督管理总局中基于中华人民共和国化妆品生产许可证相关数据
动态加载数据
首页中对应的企业信息数据是通过ajax动态请求到的
通过对详情页url的观察发现:
rul的域名都是一样的,只有携带的参数(id)不一样
id值可以从首页对应的ajax请求到的json串中获取
域名和id值拼接出一个完整的企业对应的详情页的url
详情页的企业详情数据也是动态加载出来的
观察后发现:
所有的post请求的url都是一样的,只有参数id值是不同的
如果我们可以批量获取多家企业的id后,就可以将id和url形成一个完整的详情页对应详情数据的ajax请求的url
import requests
import json
id_list=[] #存储企业的id
all_data_list=[] #存储所有的企业详情数据
#批量获取不同企业的id值
url="http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
for page in range(1,419):
page=str(page)
data={
"on":"true",
"page":page,
"pageSize": "15",
"xkType":"2",
"productName":"",
"conditionType":"1",
"applyname":"",
"applysn":""
}
json_ids=requests.post(url,data,headers=headers).json()
for dic in json_ids['list']:
id_list.append(dic['ID'])
#获取企业详情数据
post_url="http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do"
for id in id_list:
data={
'id':id
}
detail_json=requests.post(post_url,data,headers=headers).json()
print(detail_json,+"-----------ending-----------")
all_data_list.append(detail_json)
#持久化存储
fp=open("./allData.json","w",encoding="utf-8")
json.dump(all_data_list,fp,ensure_ascii=False)
print("over")
这个网站做了反爬虫手动,可能会出现JSONDecodeError
聚焦爬虫:爬取页面中指定的页面内容
聚焦爬虫对应的编码流程:
指定url
发起请求
获取响应数据
数据解析
持久化存储
数据解析分类:
正则
bs4
xpath
数据解析原理概述:
解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储
1.进行指定标签的定位
2.标签或者标签对应的属性中存储的数据值进行提取(解析)
text:字符串
content:二进制
json():对象
import requests
# 爬取图片数据
url="https://c-ssl.dtstatic.com/uploads/item/201903/10/20190310163116_arcXC.thumb.1000_0.jpeg"
# content返回的是二进制形式的图片数据
img_data=requests.get(url).content
with open("./shenyouna.jpeg","wb") as fp:
fp.write(img_data)
import requests
import re
import os
# 需求:爬取糗事百科中图片版块下所有糗图图片
if __name__ == '__main__':
# 创建一个文件夹保存所有图片
if not os.path.exists('./qiutuLibs'):
os.mkdir('./qiutuLibs')
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 '
'Safari/537.36 Core/1.70.3877.400 QQBrowser/10.8.4506.400 '
}
url = 'https://www.qiushibaike.com/imgrank/'
# 使用通用爬虫对url对应的一整张页面进行爬取
page_text = requests.get(url=url, headers=headers).text
# 使用聚焦爬虫对页面所有糗图进行解析
ex = '.*?'
img_src_list = re.findall(ex, page_text, re.S)
for src in img_src_list:
# 拼接出一个完整的图片url
src = 'https:' + src
# 请求到了图片的二进制数据
img_data = requests.get(url=src, headers=headers).content
# 生成图片名称
img_name = src.split('/')[-1]
# 图片存储路径
imgPath = './qiutuLibs/' + img_name
with open(imgPath, 'wb') as fp:
fp.write(img_data)
print(img_name, '下载成功!!!')
分页版:
import requests
import re
import os
# 需求:爬取糗事百科中图片版块下所有糗图图片
if __name__ == '__main__':
# 创建一个文件夹保存所有图片
if not os.path.exists('./qiutuLibs'):
os.mkdir('./qiutuLibs')
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 '
'Safari/537.36 Core/1.70.3877.400 QQBrowser/10.8.4506.400 '
}
# 设置一个url通用模板
url = 'https://www.qiushibaike.com/imgrank/page/%d/'
for pageNum in range(1, 3):
new_url = format(url % pageNum)
# 使用通用爬虫对new_url对应的一整张页面进行爬取
page_text = requests.get(url=new_url, headers=headers).text
# 使用聚焦爬虫对页面所有糗图进行解析
ex = '.*?'
img_src_list = re.findall(ex, page_text, re.S)
for src in img_src_list:
# 拼接出一个完整的图片url
src = 'https:' + src
# 请求到了图片的二进制数据
img_data = requests.get(url=src, headers=headers).content
# 生成图片名称
img_name = src.split('/')[-1]
# 图片存储路径
imgPath = './qiutuLibs/' + img_name
with open(imgPath, 'wb') as fp:
fp.write(img_data)
print(img_name, '下载成功!!!')
bs4解析概述
数据解析的原理:
1.标签定位
2.提取标签、标签属性中存储的数据值
bs4数据解析的原理:
1.实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中
2.通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取
环境安装:
pip install bs4
pip install lxml
如何实例化BeautifulSoup对象?
①from bs4 import BeautifulSoup
②对象的实例化
1.将本地的html文档中的数据加载到该对象中
fp=open("./test.html","r",encoding="utf-8")
soup=BeautifulSoup(fp,'lxml')
2.将互联网上获取的页面源码加载到该对象中
page_text=response.text
soup=BeautifulSoup(page_text,'lxml')
bs4解析具体使用
①soup.tagName soup.tagName返回的是html中第一次出现的tagName
②soup.find(tagName) 相当于soup.tagName,返回html中第一次出现的tagName
③属性定位
soup.find('div',class_/id/attr="song")
④soup.find_all(tagName) 返回所有tagName
⑤soup.select(selector) selector是某种选择器,可以是id选择器、类选择器、标签选择器等等,返回的是一个复数列表
层级选择器: >表示一个层级,比如父与子
空格 表示多个层级,比如祖先与后代
print(soup.select('.tang>ul>li>a')[0])
获取标签之间的文本数据:
soup.a.text
soup.a.string
soup.a.get_text()
区别:
text/get_text():可以获取某一个标签中所有的文本内容
string:只可以获取该标签下面直系的文本内容
print(soup.select('.tang a')[0].text)
print(soup.select('.tang>ul>li>a')[0].string)
print(soup.select('.tang a')[0].get_text())
获取属性值:
print(soup.select('.tang > ul a')[0]['href']) # http://www.baidu.com
bs4项目实战
需求:爬虫三国演义小说所有的章节标题和章节内容
import requests
from bs4 import BeautifulSoup
url='https://www.shicimingju.com/book/sanguoyanyi.html'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
page_text=requests.get(url,headers=headers).content
# 实例化BeautifulSoup,需要将页面源码数据加载到该对象中
soup=BeautifulSoup(page_text,'lxml')
# 解析章节标题和详情页url
li_list=soup.select(".book-mulu > ul > li")
fp=open("./sanguo.txt","w",encoding='utf-8')
for li in li_list:
title=li.a.string
detail_url='https://www.shicimingju.com'+li.a['href']
# 对详情页发起请求,解析出章节内容
detail_page_text=requests.get(url=detail_url,headers=headers).content
# 解析出详情页中相关的章节内容
detail_soup=BeautifulSoup(detail_page_text,'lxml')
div_tag=detail_soup.find('div',class_='chapter_content')
# 解析到了章节的内容
content=div_tag.text
fp.write(title+":"+content+"\n")
print(title+" 爬取成功!")
fp.close()
xpath解析基础
xpath解析:最常用且最便捷高效的一种解析方式
xpath解析原理:
1.实例化一个etree的对象,且需要将被解析的页面源码数据加载到该对象中
2.调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获
xpath环境安装:
pip install lxml
如何实例化一个etree对象?
from lxml import etree
1.将本地的html文档中的源码数据加载到etree对象中
etree.parse(filePath)
2.可以将从互联网上获取的源码数据加载到该对象中
etree.HTML('page_text')
xpath('xpath表达式')
xpath表达式
①/斜杠表示从根结点开始定位,且表示的是一个层级
②//双斜杠表示多个层级,可以表示从任意位置开始定位
③属性定位 eg.[@class="song"]
r=tree.xpath('//div[@class="song"]')
④索引定位 eg.'//div[@class="song"]/p[3]'
索引从1开始
r=tree.xpath('//div[@class="song"]/p[3]')
⑤得到文本数据
/text() 获取的是标签中直系的文本内容
//text() 获取的是标签中非直系的文本内容(所有的文本内容)
r=tree.xpath('//div[@class="tang"]//li[5]/a/text()')[0]
⑥取属性
/@attrName
r=tree.xpath('//div[@class="song"]/img/@src')
xpath实战
58二手房
import requests
from lxml import etree
# 爬取页面源码数据
url='https://bj.58.com/ershoufang/'
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
page_text=requests.get(url,headers=headers).text
# 数据解析
tree=etree.HTML(page_text)
titles=tree.xpath('//h3[@class="property-content-title-name"]/@title')
fp=open("./58.txt","w",encoding="utf-8")
for title in titles:
fp.write(title+"\n")
fp.close()
4k图片解析下载
通用处理中文乱码的解决方案:
img_name=li.xpath('./a/img/@alt')[0]+".jpg"
img_name=img_name.encode("iso-8859-1").decode("gbk")
import requests
from lxml import etree
import os
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
url='http://pic.netbian.com/4kdongman/'
response=requests.get(url,headers=headers)
response.encoding=response.apparent_encoding
page_text=response.text
# 数据解析:src的属性值,alt属性
tree=etree.HTML(page_text)
li_list=tree.xpath('//div[@class="slist"]//li')
if not os.path.exists("./picLibs"):
os.mkdir("./picLibs")
for li in li_list:
img_src="http://pic.netbian.com"+li.xpath('./a/img/@src')[0]
img_name=li.xpath('./a/img/@alt')[0]+'.jpg'
#请求图片,进行持久化存储
img_data=requests.get(url=img_src,headers=headers).content
img_path="./picLibs/"+img_name
with open(img_path,"wb") as fp:
fp.write(img_data)
print(img_name,'下载成功!!')
全国城市名称爬取
import requests
from lxml import etree
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
url='https://www.aqistudy.cn/historydata/'
page_text=requests.get(url,headers=headers).text
tree=etree.HTML(page_text)
all_city_names=[]
# 解析到热门城市和所有城市对应的a标签
# 热门城市a标签的层级关系:div/ul/li/a
# 全部城市a标签的层级关系:div/ul/div[2]/li/a
a_list=tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')
for a in a_list:
city_name=a.xpath('./text()')[0]
all_city_names.append(city_name)
print(all_city_names,len(all_city_names))
爬取站长素材中免费简历模板
import requests
from lxml import etree
import os
url='https://sc.chinaz.com/jianli/free.html'
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
response=requests.get(url,headers=headers)
response.encoding=response.apparent_encoding
page_text=response.text
if not os.path.exists("./intro"):
os.mkdir("./intro")
tree=etree.HTML(page_text)
a_list=tree.xpath('//div[@id="main"]/div/div/a')
for a in a_list:
a_href=a.xpath('./@href')[0]
img_alt='./intro/'+a.xpath('./img/@alt')[0]+'.rar'
res_detail=requests.get(a_href,headers=headers)
res_detail.encoding=res_detail.apparent_encoding
detail=res_detail.text
detail_tree=etree.HTML(detail)
detail_download=detail_tree.xpath('//div[@id="down"]/div[2]/ul/li[1]/a/@href')[0]
res=requests.get(detail_download,headers=headers).content
with open(img_alt,"wb") as fp:
fp.write(res)
print(img_alt,"下载成功!!")
爬取效果:
Day4
验证码识别简介
验证码是一种反爬机制。需要识别验证码图片中的数据,用于模拟登录操作
识别验证码的操作:
人工肉眼识别(不推荐)
第三方自动识别
云打码使用流程
import json
import time
import requests
import base64
class YdmVerify(object):
_custom_url = "http://api.jfbym.com/api/YmServer/customApi"
_token = "$"
_headers = {
'Content-Type': 'application/json'
}
def common_verify(self, image, verify_type="10110"):
# 数英汉字类型
# 通用数英1-4位 10110
# 通用数英5-8位 10111
# 通用数英9~11位 10112
# 通用数英12位及以上 10113
# 通用数英1~6位plus 10103
# 定制-数英5位~qcs 9001
# 定制-纯数字4位 193
# 中文类型
# 通用中文字符1~2位 10114
# 通用中文字符 3~5位 10115
# 通用中文字符6~8位 10116
# 通用中文字符9位及以上 10117
# 中文字符 1~4位 plus 10118
# 定制-XX西游苦行中文字符 10107
# 计算类型
# 通用数字计算题 50100
# 通用中文计算题 50101
# 定制-计算题 cni 452
payload = {
"image": base64.b64encode(image).decode(),
"token": self._token,
"type": verify_type
}
resp = requests.post(self._custom_url, headers=self._headers, data=json.dumps(payload))
print(resp.text)
return resp.json()['data']['data']
if __name__ == '__main__':
Y = YdmVerify()
with open('2.png', 'rb') as f:
s = f.read()
Y.common_verify(image=s)
识别古诗文网登录页面中的验证码
①将验证码图片进行本地下载
②调用平台提供的示例代码进行图片数据识别
import requests
from lxml import etree
import base64
import json
# 将验证码图片下载到本地
url='https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
page_text=requests.get(url,headers=headers).text
tree=etree.HTML(page_text)
code_img_src="https://so.gushiwen.cn"+tree.xpath('//img[@id="imgCode"]/@src')[0]
img_data=requests.get(url=code_img_src,headers=headers).content
with open('./code.jpg','wb') as fp:
fp.write(img_data)
class YdmVerify(object):
_custom_url = "http://api.jfbym.com/api/YmServer/customApi"
_token = "$"
_headers = {
'Content-Type': 'application/json'
}
def common_verify(self, image, verify_type="10110"):
# 数英汉字类型
# 通用数英1-4位 10110
# 通用数英5-8位 10111
# 通用数英9~11位 10112
# 通用数英12位及以上 10113
# 通用数英1~6位plus 10103
# 定制-数英5位~qcs 9001
# 定制-纯数字4位 193
# 中文类型
# 通用中文字符1~2位 10114
# 通用中文字符 3~5位 10115
# 通用中文字符6~8位 10116
# 通用中文字符9位及以上 10117
# 中文字符 1~4位 plus 10118
# 定制-XX西游苦行中文字符 10107
# 计算类型
# 通用数字计算题 50100
# 通用中文计算题 50101
# 定制-计算题 cni 452
payload = {
"image": base64.b64encode(image).decode(),
"token": self._token,
"type": verify_type
}
resp = requests.post(self._custom_url, headers=self._headers, data=json.dumps(payload))
print(resp.text)
return resp.json()['data']['data']
Y = YdmVerify()
with open('code.jpg', 'rb') as f:
s = f.read()
code_text=Y.common_verify(image=s)
print(code_text)