爬虫
通过程序来获取到互联网上的资源
-
爬虫在使用场景中的分类
1、通用爬虫:抓取系统重要组成部分。抓取的是一整张页面数据
2、聚焦爬虫:是建立在通用爬虫的基础上。抓取的是页面特定的局部的内容
3、增量式爬虫:检测网站中数据更新的情况。只会抓取网站中最新更新出来的数据 -
反爬机制:
门户网站,可以通过制定相应的策略或是技术手段,防止爬虫程序进行网站数据的爬取
-
反反爬策略:
爬虫程序可以制定相关的策略或是技术手段,破解门户网站中具备的反爬机制,获取相关数据
-
robots.txt协议
君子协议:规定了网站中哪些数据可以被爬虫哪些数据不可以被爬虫
查看淘宝的robots.txt协议:
Disallow:后面的数据是不可以爬的
-
http协议
概念:就是服务器和客户端进行数据交互的一种形式 常用请求信息: User-Agent:请求载体的身份标识 Connection:请求完毕以后,是断开连接还是保持连接 常用响应信息: Content-Type:服务器响应回客户端的数据类型 https协议: 安全的http协议(涉及数据加密) 加密方式: 对称密钥加密 非对称密钥加密 证书密钥加密(https)
-
请求头中最常见的一些重要内容(爬虫需要)
1、User-Agent:请求载体的身份标识(用啥发送的请求) 2、Referer:防盗链(这次请求是从哪个页面来的?反爬会用到) 3、cookie:本地字符串数据信息(用户登录信息,反爬的token)
-
响应头中的一些重要内容:
1、cookie:本地字符串数据信息(用户登录信息,反爬的token) 2、各种神奇的莫名其妙的字符串(整个需要验证,一般都是token字样,防止各种攻击和反爬)
-
请求方式:
1、GET:显示提交 一般查询东西的时候,用到比较多 2、POST:隐式提交 对数据做更改上传内容的时候用的比较多
requests模块:
python中原生的一款基于网络请求的模块,功能非常强大,简单便捷,效率高效
作用:模拟浏览器发请求。
-
如何使用:(requests模块的编码流程)
1、指定url 2、发起请求 3、获取响应数据 4、持久化存储
-
环境安装:
pip install requests
实例1:简易网页采集器
-
反扒机制:
UA检测:门户网站的服务器会检测对应请求的载体身份标识,如果检测到请求的载体身份标识是一款浏览器,说明是一个正常请求,服务器端就不会拒绝,如果不是就表示是一个不正常的请求(爬虫),服务器端就会拒绝。
UA:User-AgentUA伪装:让爬虫对应的请求载体身份标识伪装成一款浏览器
import requests
url='https://www.sogou.com/web'
#处理url携带的参数:封装到字典当中
kw=input('enter word:')
param={
'query':kw
}
#UA伪装:将对应的User-Agent封装到一个字典当中
headers={"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44"}
#对指定的url发起请求对应的url是携带参数的,并且请求过程中处理了参数
resp=requests.get(url=url,params=param,headers=headers)
page_text=resp.text
fileName=kw+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
fp.write(page_text)
print(fileName,'保存成功')
运行结果:
运行这个页面就可以获得小狗的相关网页
实例2:破解百度翻译
百度翻译会局部刷新(输入单词后不需要回车等操作,自动出现信息)
对应的请求是一个POST请求(携带了参数)
响应数据是一组json数据
import requests
import json
#1、指定url
url="https://fanyi.baidu.com/sug"
#2、进行UA伪装
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'}
#3、post请求参数处理(和get请求一致)
word=input('enter a word')
data={
'kw':'word'}
#4、请求发送
resp=requests.post(url=url,data=data,headers=headers)
#5、获取响应数据,json返回的是obj(如果确认响应数据是json,才可以使用)
dic_obj=resp.json()
print(dic_obj)
#持久化存储
fileName=word+'.json'
fp=open(fileName,'w',encoding='utf-8')
json.dump(dic_obj,fp=fp,ensure_ascii=False)
fp.close()
运行结果:
实例3:豆瓣电影
选择豆瓣电影排行榜中的喜剧类型
在这个页面当我们滑动滚轮到下面的时候就会出现新的数据,所以是局部刷新,就说明存在ajax请求
import requests
import json
url='https://movie.douban.com/j/chart/top_list'
param={
'type': '24',
'interval_id': '100:90',
'action': '',
'start': '0',#从库中的第几部电影去取
'limit': '20'#一次取出的个数
}
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
resp=requests.get(url=url,params=param,headers=headers)
list_data=resp.json()
fp=open('./douban.json','w',encoding='utf-8')
json.dump(list_data,fp,ensure_ascii=False)
fp.close()
print('over!!')
运行结果:
数据解析概述
-
三种解析方式: 1、re解析 2、bs4解析 3、xpath解析
-
数据解析原理概述:
解析的局部的文本内容都会在标签之间或是标签对应的属性当中进行存储 1、 进行指定标签的定位 2、标签或者标签对应的属性中存储的数据进行提取(解析)
这三种方式可以混合使用,完全以结果做导向,只要能拿到你想要的数据,用什么方案并不重要,当你掌握了这些之后,在考虑性能的问题。
-
编码流程:
1、指定url 2、发起请求 3、获取响应数据 4、数据解析 5、持久化存储
re解析:
正则表达式:一种使用表达式的方式对字符串进行匹配的语法规则
元字符:具有固定含义的特殊符号
常用元字符:
. 匹配除换行符以外的任意字符//一个.就是任意一个字
\w 匹配字母或数字或下划线
\s 匹配任意的空白符
\d 匹配数字
\n 匹配一个换行符
\t 匹配一个制表符
^ 匹配字符串的开始
$ 匹配字符串的结尾
\W 匹配非字母或数字或下划线
\D 匹配非数字
\S 匹配非空白符
a|b 匹配字符a或字符b
() 匹配括号内的表达式,也是一个组
[…] 匹配字符组中的字符
[^…] 匹配除了字符组中字符的所有字符
量词:控制前面的元字符出现的次数
1 * 重复零次或更多次
2 + 重复一次或更多次
3 ? 重复零次或一次
4 {n} 重复n次
5 {n,} 重复n次或更多次
6 {n,m} 重复n次到m次
贪婪匹配和惰性匹配
1 .* 贪婪匹配
2 .*?//尽可能少的匹配内容 惰性匹配
写爬虫用到最多的就是惰性匹配
re模块
import re
#findall:匹配字符串中所有符合正则的内容
#r是用来规避反斜杠的转义字符转义
list=re.findall(r"\d+","我的电话是:10086,我女朋友的电话是:100010")
print(list)
#finditer:匹配字符串中所有内容,返回的是迭代器
it=re.finditer(r"\d+","我的电话是:10086,我女朋友的电话是:100010")
print(it)
for i in it:
print(i)
print(i.group())
运行结果:
#search返回的结果是match对象,拿数据需要.group()
#特点:全文检索,检索到一个就返回
s=re.search(r"\d+","我的电话是:10086,我女朋友的电话是:100010")
print(s.group())#输出10086
#match是从头开始匹配
s=re.match(r"\d+","我的电话是:10086,我女朋友的电话是:100010")
print(s.group())#结果显示匹配不成功
s=re.match(r"\d+","10086,我女朋友的电话是:100010")
print(s.group())#输出10086
#预加载正则表达式
obj=re.compile(r"\d+")
ret=obj.finditer("我的电话是:10086,我女朋友的电话是:100010")
for i in ret:
print(i.group())#输出结果是10086 100010
s="""
<div class='jay'><span id='1'>迪丽热巴</span></div>
<div class='jj'><span id='2'>李知恩</span></div>
<div class='jolin'><span id='3'>月熊</span></div>
<div class='sylar'><span id='4'>阿狸阿狸</span></div>
"""
obj=re.compile(r"<div class='.*?'><span id='\d+'>.*?</span></div>",re.S)
#re.S作用是让。能够匹配换行符
res=obj.finditer(s)
for i in res:
print(i.group())
obj=re.compile(r"<div class='.*?'><span id='\d+'>(?P<name>.*?)</span></div>",re.S)
#re.S作用是让。能够匹配换行符
#(?P<名字>正则)可以单独从正则匹配的内容中提取到想要的内容
res=obj.finditer(s)
for i in res:
print(i.group("name"))
运行结果:
案例一:实例实现提取豆瓣电影信息(名字,评分,年份,多少人评价)
#拿到页面源代码 requests
#通过re来提取想要的内容 re
import requests
import re
import csv
url="https://movie.douban.com/top250"
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37"}
resp=requests.get(url,headers=headers)
#print(resp.text)#检查是否拿到页面源代码
page_content=resp.text
#解析数据
obj=re.compile(r'<li>.*?<div class="item">.*?<span class="title">(?P<name>.*?)'
r'</span>.*?<p class="">.*?<br>(?P<year>.*?) .*?<span'
r' class="rating_num" property="v:average">(?P<score>.*?)</span>.*?'
r'<span>(?P<num>.*?)人评价</span>',re.S)
#开始匹配
res=obj.finditer(page_content)
f=open("data.csv",mode="w",newline='',encoding="utf-8")
csvwriter=csv.writer(f)
for i in res:
# print(i.group("name"))
# print(i.group("score"))
# print(i.group("num"))
# print(i.group("year").strip())
dic=i.groupdict()
dic['yaer']=dic['year'].strip()
csvwriter.writerow(dic.values())
f.close()
print("over!")
得到文件
里面包含了数据信息
bs4进行数据解析
-
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=reaponse.text soup=BeautifulSoup(page_text,'lxml')
-
提供的用于数据解析的方法和属性:
from bs4 import BeautifulSoup fp=open('./mybaidu.html','r',encoding='utf-8') soup=BeautifulSoup(fp,'lxml') print(soup.a)
运行结果:得出的是第一个a标签内容
就是说soup.tagName:返回的是文档中第一次出现的tagName对应的标签
print(soup.div)#就是获取div里面的内容
print(soup.find('div'))#等同于print(soup.div)
print(soup.find('div',class_=''))#获得具体的div(class可以改成id等等属性)
print(soup.find_all('a'))#获得所有a标签内容
-
select函数:
print(soup.select('某种选择器(id,class,标签)'))#返回的是一个列表 print(soup.select('.tang > ul >li > a'))#层级选择器 >表示一个层级,空格表示多个层级
-
获取标签之间的文本数据
soup.a.text/string/get_text() text/get_text():可以获取某一个标签中所有的文本 string:只可以获取该标签下面直系的文本内容
-
获取标签中属性值:
soup.a['href']
实例一抓取三国演义的目录以及章节内容:
网页网址:https://www.shicimingju.com/book/sanguoyanyi
获得a标签就可以获得文本内容:
文本内容:
都是在p标签当中
#需求:爬取三国演义小说所有的章节标题和章节内容
#对首页的页面数据进行爬取
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) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49'
}
page_text=requests.get(url=url,headers=headers).text.encode('ISO-8859-1')
#在首页中解析出章节的标题和详情页的url
#1、实例化BeautifulSoup对象,需要将页面源码数据加载到该对象中
soup=BeautifulSoup(page_text,'lxml')
#解析章节标题和详情页的url
li_list=soup.select('.book-mulu > ul > li')
fp=open('./sanguoyanyi.txt','w',encoding='utf-8')
for li in li_list:
title=li.a.string
detail_url='http://www.shicimingju.com'+li.a['href']
#对详情页发起请求,解析出章节内容
detail_page_text=requests.get(url=detail_url,headers=headers).text.encode('ISO-8859-1')
#解析出详情页中相关的章节内容
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,'爬取成功!!!')
运行结果:
xpath解析:最常用且最便捷的一种二解析方式,通用性比较强
-
xpath解析原理:
1、实例化一个etree的对象,且需要将被解析的页面源码数据加载到该对象中 2、调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获
-
如何实例化一个etree对象:
1、将本地的html文档中的源码数据加载到etree对象中 etree.parse(filepath) 2、可以从互联网上获取的源码数据加载到该对象中 etree.HTML('page_text')
-
xpath(‘xpath 表达式’)
xpath表达式: /:表示的是从根节点开始定位。表示的是一个层级 r=tree.xpath('/html/body/div') //:表示的是多个层级,可以表示从任意位置开始 r=tree.xpath('/html//div')#和上面的结果一样 属性定位://div[@class="song"]-->tag[@attrName="attrVlue"] 索引定位://div[@class="song"]/p[3]索引是从1开始的 取文本: /text():获取的是标签中直系都文本内容 //tree.xpath('div[@class="tang"]//li[5]/a/text()')[0] //text():标签中非直系的文本内容(所有的文本内容) 取属性值:/@attrName div[@class="song"]/img/@src
实例1:全国城市名称爬取
网页网址:(https://www.aqistudy.cn/historydata/)
检查网页可以直到,热门城市的名字都在li标签中
同理可得全部的城市的名称也在li标签中
代码如下:
import requests
from lxml import etree
url='https://www.aqistudy.cn/historydata/'
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49'
}
page_text=requests.get(url=url,headers=headers).text
tree=etree.HTML(page_text)
hot_li_list=tree.xpath('//div[@class="bottom"]/ul/li')
all_city_names=[]
#解析到了热门城市的城市名字
for li in hot_li_list:
hot_name=li.xpath('./a/text()')[0]
all_city_names.append(hot_name)
#解析的是全部城市的名称
all_city_list=tree.xpath('//div[@class="bottom"]/ul/div[2]/li')
for li in all_city_list:
city_name=li.xpath('./a/text()')[0]
all_city_names.append(city_name)
print(all_city_names,len(all_city_names))
运行结果:
使用一种通用的代码实现:
import requests
from lxml import etree
url='https://www.aqistudy.cn/historydata/'
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49'
}
page_text=requests.get(url=url,headers=headers).text
tree=etree.HTML(page_text)
#解析热门城市和全部城市对应的a标签
#div[@class="bottom"]/ul/li/a 热门城市a标签的层级关系
#div[@class="bottom"]/ul/div[2]/li/a 全部城市a标签的层级关系
a_list=tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')
all_city_names=[]
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))
运行结果:
运行结果和上面是一样的