爬虫基础
目录:
requests
- 请求操作
- 数据解析
- 高级操作
- 代理
- cookie
- 模拟登陆
- 异步爬虫
- selenium
- js逆向
scrapy
- 常用操作
爬虫:通过编写程序让其模拟浏览器上网,去互联网中爬取数据的过程
爬虫的分类:
- 通用爬虫
- 爬取一整张页面源码数据
- 聚焦爬虫
- 爬取页面中局部的指定数据
- 聚焦爬虫是建立在通用爬虫的基础之上
- 增量式爬虫
- 监测网站数据更新的情况.
- 分布式爬虫
反爬机制
反反爬策略
- robots协议:存在于服务器端的文本协议,协议中就指定好了网站中哪些数据能爬哪些数据不可以爬取
requests模块的基础
介绍:基于网络请求的一个模块
下载:
pip install requests
编码流程:
- 指定url
- 发起请求
- 获取响应数据
- 持久化存储
参数动态化
- 异常的请求:不是通过浏览器发起的请求都是异常请求.
- User-Agent:请求载体的身份标识
- 反爬机制:UA检测
- 反反爬策略:UA伪装
基础爬虫,例:爬取搜狗首页数据
import requests
url = "https://www.sogo.com/"
# 拿到响应数据
response = requests.get(url=url).text
print(response)
动态加载数据的爬取
判断是否动态加载数据
<br> (二维码自动识别)
进行局部搜索 ctrl+F
http://wap.kfc.com.cn (二维码自动识别)
对一个网站数据进行爬取前:
1.查看我们想要爬取的数据是否为动态加载
- 如果页面中的数据不是可以直接通过地址栏的url爬取到的就为动态加载数据
- 检测方式:通过抓包工具查看preview或者进行局部搜索
2.如何爬取动态加载的数据?
- 基于抓包工具进行全局搜索,可以定位到动态加载数据对应的数据包
- 从数据包中就能提取出
url
、请求方式
、请求头
- 从数据包中就能提取出
- 动态加载数据的生成方式?
- ajax请求
- js请求
例:爬取肯德基网站的地址
肯德基:http://www.kfc.com.cn/kfccda/index.aspx
headers里面的数据就是UA伪装
import requests
url = "http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36'
}
# 爬取一到六页的数据
for index in range(1, 6):
data = {
'cname': '',
'pid': '',
"keyword": "河北",
"pageIndex": index,
"pageSize": "10"
}
# 拿出所有的数据,然后要table1里面的数据
all_response = requests.post(url, headers=headers, data=data).json().get("Table1")
# 循环拿出数据
for response in all_response:
addr = response.get("storeName")
detail = response.get("addressDetail")
print(addr, detail)
最后,打印出数据
龙口东 天河北路华标广场首层
新洋 河北路1629号
乐都汇 河北大街中段115号乐都汇商场一层
滨河 张家口滨河北路一、二层
河北路 塘沽区河北路5348号
明华 黄河北大街78号
例:解决数据乱码
# 问题
# 爬取的数据出现了乱码
key = input('enter a key word:')
# 参数动态化
params = {
'query':key
}
url = 'https://www.sogou.com/web'
# 动态指定请求的参数
response = requests.get(url=url,params=params)
# 修改响应数据的编码格式
response.encoding = 'utf-8'
page_text = response.text
fileName = key+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
fp.write(page_text)
例:多层爬虫,荣耀商店
荣耀店铺信息:https://m.vmall.com/help/hnrstoreaddr.htm ,爬取多层
思路:
1.查看详情页中的数据是否为动态加载? 发现数据为动态加载数据
2.捕获动态加载数据? 基于抓包工具进行全局搜索 从定位到的数据包中分析出:请求参数shopId为动态变化的
结论:首先先要获取每一家店铺的shopId,然后在进行店铺详情页的数据抓取
3.如何获取每一家店铺的shopId? 在首页捕获
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36'
}
url = "https://openapi.vmall.com/mcp/offlineshop/getShopList"
json_data = {"portal":2,"lang":"zh-CN","country":"CN","brand":1,"province":"北京","city":"北京","pageNo":1,"pageSize":20}
# 注意:如果请求参数不是键值对,而是一个字典,则请求中一定要使用json参数
# 此时response是一个列表
response = requests.post(url, headers=headers, json=json_data).json().get("shopInfos")
for res in response:
id = res.get("id")
# 现在我们拿到详情地址的url去爬到里面去
# 因为每次访问不同的详情页的时候,shopid都会发生变化,所以给他一个动态参数
detail_url = "https://openapi.vmall.com/mcp/offlineshop/getShopById?portal=2&version=10&country=CN&shopId=%d&lang=zh-CN" % id
# url里面已经包含了params,所以不必重新构造一个params
detail_res = requests.get(detail_url, headers=headers).json().get("shopInfo")
print("地址:"+ res.get("name") +",营业时间:"+ detail_res.get("serviceTime"))
注意:如果请求参数不是键值对,而是一个字典,则请求中一定要使用json参数
打印效果:
地址:荣耀授权体验店(鑫海韵通电器商城店),营业时间:9:00-19:30
地址:荣耀授权体验店(西铁营中路店),营业时间:9:00-18:00
地址:荣耀授权体验店(大兴龙湖天街店),营业时间:10:00-22:00
地址:荣耀授权体验店(天通苑中苑店),营业时间:9:30-21:30
地址:荣耀授权体验店(丰科路店),营业时间:9:00-18:00
数据解析
1.re
- 问题:浏览器的开发者工具中ELements和netword的response中都存放的是页面源码数据, 区别是什么?
- 区别在于:Elements中显示的页面源码数据是经过全局渲染的,而response中仅仅是当前 一个请求响应回来的数据.如果页面中存在动态加载数据,则Elements中可以查看到动态 加载的数据,而response中查看不到
- 站长素材图片爬取:
- 反爬:图片懒加载:页面中使用伪属性替代真正的属性
- 策略:可以在解析的时候,直接解析伪属性的值即可
例:爬取图片并保存本地(静态网页)
http://sc.chinaz.com/tupian/eluosi.html
两种方法爬取
# #图片爬取方式1:
# img_url = 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1593106255,4245861836&fm=26&gp=0.jpg'
# #content:返回的是二进制的数据
# img_data = requests.get(url=img_url,headers=headers).content
# with open('./123.png','wb') as fp:
# fp.write(img_data)
# 方式2:urllib
# from urllib import request
# img_url = 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1593106255,4245861836&fm=26&gp=0.jpg'
# request.urlretrieve(img_url,filename='./456.png')
区别:方式1可以进行UA伪装,方式2不行
爬取
import requests
import os, re
from urllib import request
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36'
}
url = "http://sc.chinaz.com/tupian/eluosi.html"
dirName = "mzlibs"
if not os.path.exists(dirName):
os.mkdir(dirName)
# 要解析拿出图片的url
page_text = requests.get(url, headers=headers).text
#使用re进行图片链接的批量提取
ex = '<div>.*?<img src2="(.*?)" alt=.*?>.*?</div>'
# s = '''<div>
# <a href="http://sc.chinaz.com/tupian/200811519845.htm" target="_blank" alt="清新氧气俄罗斯美女图片">
# <img alt="清新氧气俄罗斯美女图片" src="http://pic2.sc.chinaz.com/Files/pic/pic9/202008/apic27002_s.jpg">
# </a>
# </div>'''
img_src_list = re.findall(ex, page_text, re.S) # re.S是为了过滤掉空格和换行
for img_src in img_src_list:
img_name = img_src.split("/")[-1]
# 因为没用os,所以文件路径中间需要加/
img_path = dirName + '/' + img_name
# 下载图片到本地
request.urlretrieve(img_src, img_path)
print(img_name+"下载成功")
下载成功
2.bs4
html相关特性:
- html和xml的区别:
- 1.html的标签和属性不可以自定义,而xml就是自定义的标签和属性
- 2.html使用来展示数据的,而xml是用来存储数据
- 特性:标签都是成对出现.
html展示的数据都在哪里出现?
- 直接存储在标签之间
- 直接存储在标签的属性中
数据解析的通用原理:
- 1.标签定位
- 2.指定标签中的数据进行提取
bs4的实现流程:
- 1.环境安装:pip install bs4,pip install lxml
- 2.实例化一个BeautifulSoup对象,且将被解析的页面源码数据加载到该对象中
- 3.调用该对象中的相关方法和属性进行标签定位和数据提取
实例化一个BeautifulSoup对象的方式
- 针对解析本地存储的html文件 -BeautifulSoup(fp,'lxml')
- 针对从互联网上请求到的页面源码数据
- BeautifulSoup(page_text,'lxml')
bs4规则
标签定位
1. soup.tagName,只能定位到第一次出现的该标签,且将定位到的标签以字符串的形式进行了返回
print(soup.div)
2.实现属性定位soup.find('tagName',attrName='value'),find返回的是单数,find_all返回复数(列表)
print(soup.find('div',class_='tang'))
print(soup.find_all('div',class_='song'))
3.选择器定位
print(soup.select('.tang')) #将class为tang的所有标签定位到
层级选择器:大于号表示一个层级,空格表示多个层级
print(soup.select('.tang > ul > li > a'))
print(soup.select('.tang > ul a'))
取文本
a_tag = soup.find('a',id='feng')
print(a_tag.string) #取出标签中直系的文本内容
div_tag = soup.find('div',class_='tang')
print(div_tag.text) #取出标签下所有的文本内容,比如一个div下面的所有p标签里面的内容
取属性
a_tag = soup.find('a',id='feng')
print(a_tag['href']) #取属性
例:三国,爬取静态网页并用bs4解析
https://www.shicimingju.com/
import requests
from bs4 import BeautifulSoup
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36'
}
url = "https://www.shicimingju.com/book/sanguoyanyi.html"
page_text = requests.get(url, headers=headers).text
soup = BeautifulSoup(page_text, "lxml")
# 可以链式操作,但只会有一条数据
# print(soup.find('div', class_="book-mulu").find('ul').find('li').find('a'))
obj_list = soup.select('.book-mulu > ul > li > a')
# print(obj_list)
# [<a href="/book/sanguoyanyi/1.html">第一回·宴桃园豪杰三结义
# 斩黄巾英雄首立功</a>, <a href="/book/sanguoyanyi/2.html">第二回·张翼德怒鞭督邮
# 何国舅谋诛宦竖</a>
fp = open("./sanguo.txt", "w", encoding='utf-8')
for obj in obj_list:
title = obj.string
# print(obj['href']) # /book/sanguoyanyi/1.html ......
# 构造url,response,soup
detail_url = 'https://www.shicimingju.com%s' % obj.get("href")
detail_text = requests.get(detail_url, headers=headers).text
# 解析txt
detail_soup = BeautifulSoup(detail_text, "lxml")
detail_content = detail_soup.find('div', class_='chapter_content').text
# 写进文件中
fp.write(title + 'n' + detail_content + 'n')
print(title + '爬取成功')
fp.close()
3.xpath(最为通用)
- 解析的流程
- 1.实例化一个etree对象,且将被解析的数据加载到该对象中
- 2.调用etree对象中的xpath方法结合着不同形式的xpath表达式进行标签定位和数据提取
- etree对象的实例化
- 针对本地文件:etree.parse(filePath)
- 针对互联网:etree.HTML(page_text)
- xpath的独特的使用方式
- 可以增加xpath的通用性 有名用户://div/a/p/span/text() 匿名用户://a/div/p/text() 综合形式://div/a/p/span/text() | //a/div/p/text()
- pyquery(自学)
xpath表达式规则:
#标签定位的xpath表达式
#1.定位title标签
#最左侧的/:一定要从根节点开始进行标签定位
#非最最测的/:表示一个层级
#最左侧的//:可以从任意位置进行节点定位
#非最左侧的//:表示多个层级
print(tree.xpath('/html/head/title')[0])
print(tree.xpath('/html//title')[0])
print(tree.xpath('//title')[0])
#2.属性定位&索引定位(索引从1开始)
print(tree.xpath('//a[@id="feng"]'))
print(tree.xpath('//div[@class="tang"]/ul/li[1]'))
#数据的提取
print(tree.xpath('//div[@class="song"]/p[3]/text()')[0]) #取直系的文本内容
print(tree.xpath('//div[2]//text()'))
print(tree.xpath('//a[@id="feng"]/@href')) #取属性
数据练习
import requests
from lxml import etree
# 加载本地文件
tree = etree.parse('./test.html')
# 寻找标签
# print(tree.xpath('/html/body/div[@class="song"]/a/@title'))
print(tree.xpath('/html/body/div[@class="song"]/a')[0])
# <Element a at 0x1d6690a12c8>
print(tree.xpath('/html/body/div[@class="song"]/a'))
# [<Element a at 0x1d6690a1248>, <Element a at 0x1d6690a1308>]
print(tree.xpath('/html/body/div[@class="song"]/a/text()'))
# ['nttt', 'ntt宋朝是最强大的王朝,不是军队的强大,而是经济很强大
# ,国民都很有钱', '总为浮云能蔽日,长安不见使人愁']
print(tree.xpath('/html/body/div[@class="song"]/a/text()')[2])
# 总为浮云能蔽日,长安不见使人愁
如何在网页中获取xpath路径
复制的结果为
/html/body/div[2]/a[1]
例:利用xpath爬取美女图片并保存在本地
http://pic.netbian.com/4kmeinv
查看网页的代码,打开抓包工具,点击console,输入document.charset就会显示编码
>> document.charset
>> "GBK"
爬图片代码
import requests
from lxml import etree
import os
# 创建本地存储文件夹
dirName = "Girllibs"
if not os.path.exists(dirName):
os.mkdir(dirName)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36'
}
# 爬取十页的内容,因为第一页和其他页码的url不一样,所以重新构造一下
for index in range(1, 11):
if index == 1:
url = "http://pic.netbian.com/4kmeinv/"
else:
url = "http://pic.netbian.com/4kmeinv/index_%d.html" % index
response = requests.get(url, headers=headers)
response.encoding = 'gbk'
page_text = response.text
# 数据解析
tree = etree.HTML(page_text)
# print(tree)
# 现在里面没有text,只有属性,所以只能用@class来取到值
image_list = tree.xpath('//div[@class="slist"]/ul/li/a/img')
# 遍历Image_list然后保存到本地
for img in image_list:
# print(img.xpath('./@src'), img.xpath('./@alt'))
# ['/uploads/allimg/170705/232057-1499268057f947.jpg']['刘奕宁Lynn 白色衣服 黑色短裙 长发清新美女4K壁纸']
# 局部的数据解析:在上述操作定位到的标签表示的局部数据中进行数据解析
# 在局部数据解析中./表示的就是xpath的调用者对应的标签
# 构造图片名称和路径
img_name = img.xpath('./@alt')[0] + '.jpg'
img_path = dirName + '/' + img_name
# 爬取图片
img_url = "http://pic.netbian.com" + img.xpath('./@src')[0]
# content返回的是二进制内容
img_data = requests.get(img_url, headers=headers).content
# 保存图片到本地
with open(img_path, 'wb') as fp:
fp.write(img_data)
print(img.xpath('./@alt')[0] + '下载完了')
例:爬取糗事百科
https://www.qiushibaike.com/text/
import requests
from lxml import etree
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
}
for index in range(1, 15):
url = 'https://www.qiushibaike.com/text/page/%d/' % index
response = requests.get(url, headers=headers).text
tree = etree.HTML(response)
# 查找标签的时候,有些标签的里面的a标签有两个,所以需要加下标
res_list = tree.xpath('//div[@class="article block untagged mb15 typs_long"]/a[1]')
# print(len(res_list))
for res in res_list:
# print(res.xpath('./@href')[0]) # 格式为:/article/123589129
detail_url = 'https://www.qiushibaike.com' + res.xpath('./@href')[0]
# print(detail_url)
detail_response = requests.get(detail_url, headers=headers).text
detail_tree = etree.HTML(detail_response)
title_list = detail_tree.xpath('//div[2]/div/div[2]/div[2]/div[1]/div')[0]
# for title_index in title_list:
title = title_list.xpath('./text()')
print(title)
例:语音合成功能(百度ai)
https://ai.baidu.com/
先安装模块
pip install baidu-aip
代码:
from aip import AipSpeech
""" 你的 APPID AK SK """
APP_ID = '17272609'
API_KEY = 'h3Grp2xXGG0VeSAKlDL9gc4Q'
SECRET_KEY = 'WEEeDpICnzifwBAGF4QW4QiGgSb1u3ND'
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)
text = '闺蜜比较汉子,我教她,要做一个瓶盖拧不开淑女才有人疼,她表示接受。她拿着一罐八宝粥弱弱的走到心仪男神面前:李哥,帮我拆一下好么,我怕撕不好会伤到手的。李哥和煦一笑,接过撕开瓶口,温柔的递给闺蜜,闺蜜看李哥的表现非常开心,很自然的一把薅过八宝粥,仰头一口闷,喝完大手抹了下嘴巴,嚎了一句:真特么太好喝啦'
result = client.synthesis(text, 'zh', 1, {
'vol': 5,
'per':4
})
# 识别正确返回语音二进制 错误则返回dict 参照下面错误码
if not isinstance(result, dict):
with open('audio.mp3', 'wb') as f:
f.write(result)
小总结
- 反爬机制:
- UA检测,robots,图片懒加载
- 参数动态化
- get:params
- post:data/json
- headers字典:定义相关的请求头
- 动态加载数据
- 生成方式:ajax,js
- 检测方式:基于抓包工具进行一个局部搜索
- 如何捕获:基于抓包工具进行全局搜索
- 如果基于抓包工具进行全局搜索,搜索不到?说明什么?
- 请求到的数据是经过加密 request高级
- cookie
- 代理
- 模拟登陆