一、爬虫介绍:
什么是爬虫?
爬虫就是通过编写程序模拟浏览器上网,然后让其去互联网上抓取数据的过程。
哪些语言可以实现爬虫
1.php:可以实现爬虫。php被号称是全世界最优美的语言(当然是其自己号称的,就是王婆卖瓜的意思),但是php在实现爬虫中支持多线程和多进程方面做的不好。
2.java:可以实现爬虫。java可以非常好的处理和实现爬虫,是唯一可以与python并驾齐驱且是python的头号劲敌。但是java实现爬虫代码较为臃肿,重构成本较大。
3.c、c++:可以实现爬虫。但是使用这种方式实现爬虫纯粹是是某些人(大佬们)能力的体现,却不是明智和合理的选择。
4.python:可以实现爬虫。python实现和处理爬虫语法简单,代码优美,支持的模块繁多,学习成本低,具有非常强大的框架(scrapy等)且一句难以言表的好!没有但是!
爬虫的分类
1.通用爬虫:
通用爬虫是搜索引擎(Baidu、Google、Yahoo等)“抓取系统”的重要组成部分。主要目的是将互联网上的网页下载到本地,形成一个互联网内容的镜像备份。 简单来讲就是尽可能的;把互联网上的所有的网页下载下来,放到本地服务器里形成备分,在对这些网页做相关处理(提取关键字、去掉广告),最后提供一个用户检索接口。
.
搜索引擎如何抓取互联网上的网站数据?
1.门户网站主动向搜索引擎公司提供其网站的url
2.搜索引擎公司与DNS服务商合作,获取网站的url
3.门户网站主动挂靠在一些知名网站的友情链接中
2.聚焦爬虫:
聚焦爬虫是根据指定的需求抓取网络上指定的数据。例如:获取豆瓣上电影的名称和影评,而不是获取整张页面中所有的数据值。
robots.txt协议
如果自己的门户网站中的指定页面中的数据不想让爬虫程序爬取到的话,那么则可以通过编写一个robots.txt的协议文件来约束爬虫程序的数据爬取。robots协议的编写格式可以观察淘宝网的robots(访问www.taobao.com/robots.txt即可)。但是需要注意的是,该协议只是相当于口头的协议,并没有使用相关技术进行强制管制,所以该协议是防君子不防小人。但是我们在学习爬虫阶段编写的爬虫程序可以先忽略robots协议。
反爬虫
门户网站通过相应的策略和技术手段,防止爬虫程序进行网站数据的爬取。
反反爬虫
爬虫程序通过相应的策略和技术手段,破解门户网站的反爬虫手段,从而爬取到相应的数据。
二、requests模块学习
什么是requests模块
requests模块是python中原生的基于网络请求的模块,其主要作用是用来模拟浏览器发起请求。功能强大,用法简洁高效。在爬虫领域中占据着半壁江山的地位。
使用requests模块的优势:
自动处理url编码
自动处理post请求参数
简化cookie和代理操作
通过5个基于requests模块的爬虫项目对该模块进行学习和巩固
基于requests模块的get请求
--- 需求:爬取搜狗指定词条搜索后的页面数据
基于requests模块的post请求
--- 需求:登录豆瓣电影,爬取登录成功后的页面数据
基于requests模块ajax的get请求
--- 需求:爬取豆瓣电影分类排行榜 https://movie.douban.com/中的电影详情数据
基于requests模块ajax的post请求
--- 需求:爬取肯德基餐厅查询http://www.kfc.com.cn/kfccda/index.aspx中指定地点的餐厅数据
综合练习
--- 需求:爬取搜狗知乎指定词条指定页码下的页面数据
爬虫小项目联系:
#爬取搜狗首页的页面数据
#1.指定url
#2.基于requests模块的请求发送
#3.获取响应对象中的数据值
#4.持久化存储
import requests
url = 'https://www.sogou.com/'
#2.调用get方法,该方法就可以根据指定的url发起请求,返回一个响应对象
response = requests.get(url=url,proxies={'https':'121.139.218.165:31409'})
#3.text属性就可以将响应对象中的数据值进行获取,text属性返回的数据类型是str
page_text = response.text
with open('./sogou.html','w',encoding='utf-8') as fp:
fp.write(page_text)
response.text # 返回的数据类型是str
response.content #返回的是二进制的页面数据
response.status_code # 返回状态码
response.url # 返回url
response.encoding #返回的是响应对象中存储数据的原始编码格式
// 爬取搜狗指定词条搜索后的页面数据
word = input('enter a word:')
url = 'https://www.sogou.com/web?'
#如果url携带了参数,我们最好需要将参数封装到一个字典中
param = {
'query':word
}
#将封装好的参数作业到请求中
response = requests.get(url=url,params=param)
page_text = response.text
fileName = word+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
fp.write(page_text)
// 需求:登录豆瓣电影,爬取登录成功后的页面数据
import requests
url = 'https://www.douban.com/accounts/login'
#对post请求携带的参数进行字典封装
data = {
"source": "index_nav",
"form_email": "15027900535",
"form_password": "bobo@15027900535",
}
response = requests.post(url=url,data=data)
page_text = response.text
with open('douban.html','w',encoding='utf-8') as fp:
fp.write(page_text)
#爬取豆瓣电影分类排行榜 https://movie.douban.com/中的电影详情数据
import requests
url = 'https://movie.douban.com/j/chart/top_list?'
param = {
"type": "13",
"interval_id": "100:90",
"action": "",
"start": "100",
"limit": "20",
}
response = requests.get(url=url,params=param,proxies={'https':'121.139.218.165:31409'})
print(response.text)
// 需求:爬取肯德基餐厅查询http://www.kfc.com.cn/kfccda/index.aspx中指定地点的餐厅数据
import requests
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
city = input('enter a city:')
data = {
"cname":"",
"pid":"",
"keyword":city,
"pageIndex":"2",
"pageSize":"10",
}
response = requests.post(url=url,data=data)
response.text
#需求:爬取博客园指定页面下的页面数据
import requests
import os
url = 'https://www.cnblogs.com/#p'
#新建一个文件夹
if not os.path.exists('boke'):
os.mkdir('boke')
#提供一组页码的范围
start_page = int(input('enter a start page:'))
end_page = int(input('enter a end page:'))
for page in range(start_page,end_page+1):
url =url + str(page)
response = requests.get(url=url,proxies={'https':'121.139.218.165:31409'})
page_text = response.text
fileName = str(page)+".html"
filePath = './boke/'+fileName
with open(filePath,'w',encoding='utf-8') as fp:
fp.write(page_text)
print('第%d页下载成功'%page)
三、反爬
反爬机制:UA验证
反反爬机制:UA身份的伪装
流程:封装一个请求头字典(UA)。将该字典作用到请求对象中
自制定请求头信息
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36"
}
response = requests.post(url=url,data=data,headers=headers)
四、三种数据解析方式学习
1. re模块解析方式
提取出python
key="javapythonc++php"
re.findall('python',key)[0]
提取出hello world
key="<html><h1>hello world<h1></html>"
re.findall('<h1>(.*)<h1>',key)[0]
提取170
string = '我喜欢身高为170的女孩'
re.findall('\d+',string)[0]
提取出http://和https://
key='http://www.baidu.com and https://boob.com'
re.findall('https?://',key)
提取出hello
key='lalala<hTml>hello</HtMl>hahah' #输出<hTml>hello</HtMl>
re.findall('<[hH][tT][mM][lL]>(.*)</[hH][tT][mM][lL]>',key)[0]
提取出hit. :贪婪模式:尽可能多的匹配数据
key='bobo@hit.edu.com'#想要匹配到hit.
re.findall('h.*?.',key)
匹配sas和saas
key='saas and sas and saaas'
re.findall('sa{1,2}s',key)
匹配出i开头的行
string = '''fall in love with you
i love you very much
i love she
i love her'''
re.findall('^i.*',string,re.M)
匹配全部行
string1 = """<div>细思极恐
你的队友在看书
你的闺蜜在减肥
你的敌人在磨刀
隔壁老王在练腰
</div>"""
re.findall('.*',string1,re.S)
// 项目需求:爬取糗事百科指定页面的糗图,并将其保存到指定文件夹中
import requests
import re
import os
if not os.path.exists('qiutu'):
os.mkdir('qiutu')
url = 'https://www.qiushibaike.com/pic/'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36'
}
response = requests.get(url=url,headers=headers)
page_text = response.text
#4.数据解析(正则):图片
img_url_list = re.findall('<div class="thumb">.*?<img src="(.*?)".*?>.*?</div>',page_text,re.S)
for img_url in img_url_list:
#获取的是图片完整的url
img_url = "https:"+img_url
#针对图片url发起指定的请求,获取图片二进制的数据值
img_response = requests.get(url=img_url,headers=headers)
#获取响应对象中图片二进制的数据值
img_data = img_response.content
imgName = img_url.split('/')[-1]
imgPath = 'qiutu/'+imgName
#将图片数据存储到磁盘
with open(imgPath,'wb') as fp:
fp.write(img_data)
print(imgName+'下载成功')
2. 基于xpath解析方式
基于xpath解析的实现原理:
1.实例化一个etree对象,然后将页面数据封装到该对象
2.调用etree对象中的xpath函数实现解析操作
3.将指定的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
使用xpath表达式进行数据解析步骤
1.下载:pip install lxml
2.导包:from lxml import etree
3.将html文档或者xml文档转换成一个etree对象,然后调用对象中的方法查找指定的节点
2.1 本地文件:
tree = etree.parse(文件名)
tree.xpath("xpath表达式")
2.2 网络数据:
tree = etree.HTML(网页内容字符串)
tree.xpath("xpath表达式")
// 需求:爬取boss直聘的岗位信息
import requests
from lxml import etree
job = input('enter a job:')
url = 'https://www.zhipin.com/job_detail/?'
param = {
'query':job
}
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/69.0.3497.81 Safari/537.36'
}
response = requests.get(url=url,params=param,headers=headers)
page_text = response.text
#解析:
#1.获取所有岗位的链接
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="job-list"]/ul/li')
#只用Element类型的对象可以调用xpath方法
for li in li_list:
job_url = li.xpath("./div/div[1]/h3/a/@href")[0]# .li对象表示的局部页面内容
job_url = "https://www.zhipin.com" + job_url
#对job_url发起请求,获取岗位对应的详情页面
secondPage_text = requests.get(url=job_url,headers=headers).text
tree = etree.HTML(secondPage_text)
#解析岗位名称
jobName = tree.xpath('//div[@class="info-primary"]/div[2]/h1/text()')[0]
salary = tree.xpath('//div[@class="info-primary"]/div[2]/span/text()')[0].strip('\n\t')
detail = tree.xpath('//div[@class="info-primary"]/p//text()')[0]
company = tree.xpath('//div[@class="info-company"]/h3/a/text()')[0]
jobDesc = tree.xpath('//div[@class="detail-content"]/div[1]/div//text()')[0]
#持久化存储mysql/redis等
3、BeautifulSoup解析
使用流程:
- 导包: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获取到的结果为None,而其它两个,可以获取文本内容
(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选择器返回永远是列表,需要通过下标提取指定的对象
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)
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)
cap += 1