我的博客,欢迎阅读 https://blog.starmeow.cn
爬虫原理和网页构造
网络连接原理:
- 计算机----Request(请求头和消息体)--->服务器
- 计算机<---Respone(HTML文件)----服务器
爬虫原理:
- 模拟计算机对服务器发起Request请求;
- 接收服务器的Response内容并解析、提取所需的信息
设计爬虫流程
多页面和跨页面爬虫流程。
多页面爬虫流程
网页存在多页的情况,每页结构相同或相似。
- 手动翻页观察各URL构成特点,构造成所有页面的URL存入列表;
- 根据URL列表依次循环取出URL;
- 定义爬虫函数;
- 循环调用爬虫函数,存储数据;
- 循环完毕,结束爬虫程序。
跨页面爬虫流程
一个页面存在很多链接,每个链接进去对应一个详情页。
- 定义爬虫函数爬取列表页所有专题的URL;
- 将专题URL存入列表中(种子URL);
- 定义爬取详细页数据函数;
- 进入专题详细页面爬取详细页数据;
- 存储数据,循环完毕,结束爬虫程序。
网页构造
浏览器按F12,点击Elements可以看到网页的元素,包括HTML
、CSS
、JavaScript
爬虫初步入门
创建虚拟环境
>mkvirtualenv Crawler
>workon Crawler
>pip install requests
>pip install lxml
>pip install beautifulsoup4
爬虫三大库
Requests库
http://docs.python-requests.org/zh_CN/latest/user/quickstart.html
它的作用就是请求网站获取网页数据的。
import requests
res = requests.get(url='https://blog.starmeow.cn/blog/1/detail/')
print(res) # ,如果为40x则请求失败
print(res.text) # 显示网页所有内容和F12结果一样
后端显示的浏览器Agent信息为:python-requests/2.20.0
,意思就是通过requests库访问的
有时爬虫需要加入请求头来伪装成浏览器,以便更好地抓取数据。
请求头的使用方法
import requests
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
}
res = requests.get(url='https://blog.starmeow.cn/blog/1/detail/', headers=headers) # get方法加入请求头
print(res) # ,如果为40x则请求失败
print(res.text) # 显示网页所有内容和F12结果一样
然后再查看网页的请求Agent信息就是Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Requests库不仅有get()
方法,还有post()
方法。post()
方法用于提交表单来爬取需要登录才能获取数据的网站。
错误和异常
requests.exceptions的官方文档 http://www.python-requests.org/en/master/_modules/requests/exceptions/#RequestException
- Requests抛出一个
ConnectionError
异常,这个是网络问题(如DNS查询失败,拒绝连接等) -
Response.raise_for_status()
抛出一个HTTPError
异常,为HTTP请求返回了不成功的状态码(如网页不存在,返回404错误) - Requests抛出一个
Timeout
异常,原因为请求超时 - Requests抛出一个
TooManyRedirects
异常,为请求超过了设置的最大重定向次数
所有Requests显示抛出的异常都继承requests.exceptions.RequestException
,爬取过程如果遇到错误爬虫就会停止,可以通过try
来避免异常。
import requests
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
}
try:
res = requests.get(url='https://blog.starmeow0.cn/blog/1/detail/', headers=headers) # get方法加入请求头
print(res) # ,如果为40x则请求失败
print(res.text) # 显示网页所有内容和F12结果一样
except (ConnectionRefusedError, requests.exceptions.ConnectionError): # 出现错误会显示一下内容
print('拒绝连接')
当程序出现异常后,就不会直接报错,而是给一个提示,不会影响原来代码的运行。
BeautifulSoup库
可以轻松的解析Requests库请求的网页,并把网页源代码解析为Soup文档,以便过滤提取数据。
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/70.0.3538.77 Safari/537.36"
}
try:
res = requests.get(url='https://blog.starmeow.cn/blog/1/detail/', headers=headers) # get方法加入请求头
soup = BeautifulSoup(res.text, 'html.parser')
print(soup.prettify())
except requests.exceptions.ConnectionError: # 出现错误会显示一下内容
print('拒绝连接')
看上去与res.text
结果类似,但通过BeautifulSoup库解析得到的soup文档按照标准缩进格式的结构输出,为结构化的数据,为数据的过滤提取做好准备。
BeautifulSoup库的主要解析器
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup, “html.parser”) | - Python的内置标准库- 执行速度适中- 文档容错能力强 | Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差 |
lxml HTML 解析器 | BeautifulSoup(markup, “lxml”) | - 速度快- 文档容错能力强 | 需要安装C语言库 |
lxml XML 解析器 | BeautifulSoup(markup, [“lxml”, “xml”])BeautifulSoup(markup, “xml”) | - 速度快- 唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | BeautifulSoup(markup, “html5lib”) | - 最好的容错性- 以浏览器的方式解析文档- 生成HTML5格式的文档 | 速度慢,不依赖外部扩展 |
BeautifulSoup库官方推荐使用lxml作为解析器,效率更高。
标准选择器
- find_all( name , attrs , recursive , text , **kwargs ) :可根据标签名、属性、内容查找文档
- find( name , attrs , recursive , text , **kwargs ) :find返回单个元素,find_all返回所有元素
- 其他
find_all()方法
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/70.0.3538.77 Safari/537.36"
}
try:
res = requests.get(url='https://blog.starmeow.cn/blog/1/detail/', headers=headers) # get方法加入请求头
soup = BeautifulSoup(res.text, 'html.parser')
# print(soup.prettify())
find_widget = soup.find_all('div', 'widget') # 查找class="widget"的所有div标签,最终结果以list方式展示
print(find_widget)
find_thumbnail = soup.find_all('span', attrs={'class': 'thumbnail'}) # attrs参数定义一个字典参数来所有包含特殊属性的tag,包含thumbnail的class的所有span
print(find_thumbnail)
except requests.exceptions.ConnectionError: # 出现错误会显示一下内容
print('拒绝连接')
find()方法
find()
方法与find_all()
方法类似,只是find_all()
方法返回时文档汇总复核条件的所有tag,是一个集合(
),find()
方法返回的是一个Tag对象(
)
select()方法
从浏览器选择功能Copy selector,得到的结果用户信息的获取。
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/70.0.3538.77 Safari/537.36"
}
try:
res = requests.get(url='https://blog.starmeow.cn/blog/1/detail/', headers=headers) # get方法加入请求头
soup = BeautifulSoup(res.text, 'html.parser')
title = soup.select("body > section > div > div > header > h1 > a") # 右键Elements位置---Copy---Copy selector
print(title) # [创建项目初始化]
hot_titles = soup.select("body > section > aside > div.widget.widget_hot > ul > li > a > span.text") # li:nth-child(1)运行会报错,需改为li
print(hot_titles)
"""
复制结果:
body > section > aside > div.widget.widget_hot > ul > li:nth-child(1) > a > span.text
结果会报错,需要进行修改
body > section > aside > div.widget.widget_hot > ul > li:nth-of-type(1) > a > span.text 得到第一个数据
body > section > aside > div.widget.widget_hot > ul > li > a > span.text 得到所有数据
"""
for title in hot_titles:
print(title, title.get_text(), title.get_text() == title.text)
"""
【Flask微电影】05.搭建前台页面-会员登录注册和会员中心 【Flask微电影】05.搭建前台页面-会员登录注册和会员中心 True
【Flask微电影】10.搭建后台页面-会员管理、评论管理 【Flask微电影】10.搭建后台页面-会员管理、评论管理 True
【Flask微电影】11.搭建后台页面-收藏管理、日志管理 【Flask微电影】11.搭建后台页面-收藏管理、日志管理 True
【Flask微电影】07.搭建后台页面-后台登陆、后台主页页面 【Flask微电影】07.搭建后台页面-后台登陆、后台主页页面 True
【Flask微电影】01.环境搭建项目目录分析 【Flask微电影】01.环境搭建项目目录分析 True
"""
except requests.exceptions.ConnectionError: # 出现错误会显示一下内容
print('拒绝连接')
使用title.get_text()
或者title.text
可以得到标签的文字信息
Lxml库
基于libxml2虚怀若谷人XML解析库的Python封装,使用C语言编写,解析速度比BeautifulSoup快。
实例1:爬取成都地区短租房信息
思路分析
访问:https://cd.xiaozhu.com/ 查看网页的信息
分页url地址:
- https://cd.xiaozhu.com/ 或者 https://cd.xiaozhu.com/search-duanzufang-p1-0/
- https://cd.xiaozhu.com/search-duanzufang-p2-0/
- https://cd.xiaozhu.com/search-duanzufang-p3-0/
- https://cd.xiaozhu.com/search-duanzufang-p4-0/
根据url构造,只需要改变p后面的数字,可以构造出所有页面网址。然后根据这些url进入到详情页,获取标题、地址、价格、房东等信息。
爬虫代码和注释
import requests
from bs4 import BeautifulSoup
import time
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
} # 用于伪装浏览器,便于爬虫的稳定性
def get_all_links(url):
"""
得到每个分页的房子url列表
:param url: 房源列表的分页url
:return: 得到每一页的所有房源列表
"""
html_data = requests.get(url=url, headers=headers)
soup = BeautifulSoup(html_data.text, 'lxml')
# print(soup)
links = soup.select('#page_list > ul > li > a')
res = list()
for link in links:
# print(link.get('href'))
href = link.get('href') # 获取标签href属性信息,得到进入详情页的URL
res.append(href)
time.sleep(2)
return res
def get_info(url):
"""
每一个房源信息获取
:param url: 房源url
:return: 房源字典信息
"""
html_data = requests.get(url, headers=headers)
soup = BeautifulSoup(html_data.text, 'lxml')
titles = soup.select('div.wrap.clearfix.con_bg > div.con_l > div.pho_info > h4 > em')
# 复制的结果是:body > div.wrap.clearfix.con_bg > div.con_l > div.pho_info > h4 > em,无法获取,需要去掉body
title = ''
if titles:
title = titles[0].get_text().strip() # 如果得到的结果不为空,取第一个的值
addresses = soup.select('div.wrap.clearfix.con_bg > div.con_l > div.pho_info > p > span')
# print(addresses)
address = ''
if addresses:
address = addresses[0].get_text().strip()
prices = soup.select('#pricePart > div.day_l > span')
price = ''
if prices:
price = prices[0].get_text()
images = soup.select('#curBigImage')
image = ''
if images:
image = images[0].get('src')
names = soup.select('#floatRightBox > div.js_box.clearfix > div.w_240 > h6 > a')
name = ''
if names:
name = names[0].get_text()
sexs = soup.select('#floatRightBox > div.js_box.clearfix > div.member_pic > div')
sex = ''
if sexs:
sex = sexs[0].get('class')
# print(sex) # ['member_ico1']
if "member_ico1" in sex: # 根据class来判断房东的性别,男房东的class="member_ico"
sex = '女'
else:
sex = '男'
data = {
'title': title,
'address': address,
'price': price,
'image': image,
'name': name,
'sex': sex
}
# print(data)
return data
if __name__ == '__main__':
# fangzi_list = get_all_links('https://cd.xiaozhu.com/')
# fangzi_info = get_info('https://cd.xiaozhu.com/fangzi/2148542359.html')
page_urls = ['https://cd.xiaozhu.com/search-duanzufang-p{}-0/'.format(num) for num in range(1, 4)] # 准备3页数据
for page_url in page_urls:
fangzi_list = get_all_links(page_url) # 得到每一页的所有房源列表
time.sleep(2) # 暂停2秒,防止请求过快导致被服务器拒绝
for fangzi in fangzi_list:
print(get_info(fangzi)) # 遍历房源列表,得到每个房源信息
# 运行结果
# {'title': '宽窄巷子人民公园双地铁2号4号景区房熊猫风', 'address': '四川省成都市青羊区通惠门69号长富新城2栋24楼', 'price': '188', 'image': 'https://image.xiaozhustatic1.com/00,800,533/14,0,39,25214,1800,1200,71ccb694.jpg', 'name': '周周小家', 'sex': '男'}
# {'title': '地铁口春熙路太古里,熊猫基地大二居3床住5人', 'address': '四川省成都市成华区前锋路6号阳光欣园', 'price': '328', 'image': 'https://image.xiaozhustatic1.com/00,800,533/13,0,61,31508,1798,1200,d0c0cd6e.jpg', 'name': '幺妹家', 'sex': '女'}
实例2:爬取库存TOP500数据
思路分析
TOP500第一页链接:http://www.kugou.com/yy/rank/home/1-8888.html?from=rank
网页版不能手动翻页,但观察第一页的URL,将1改为2:http://www.kugou.com/yy/rank/home/2-8888.html?from=rank
正好是第二页的列表,浏览发现,每一页显示22条数据,所以500条,23页能显示完
需要爬取的信息有排名、歌手、歌曲、歌曲时长
爬虫代码和注释
import requests
from bs4 import BeautifulSoup
import time
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
} # 用于伪装浏览器,便于爬虫的稳定性
def get_info(url):
html_data = requests.get(url=url, headers=headers)
soup = BeautifulSoup(html_data.text, 'lxml')
ranks = soup.select('#rankWrap > div.pc_temp_songlist > ul > li > span.pc_temp_num') # 前3个数字加粗,select不能包含strong标签
# print(ranks[0].get_text().strip(), ranks[4].get_text().strip()) # 获取歌曲的信息
songs = soup.select('#rankWrap > div.pc_temp_songlist > ul > li > a')
length_of_times = soup.select('#rankWrap > div.pc_temp_songlist > ul > li > span.pc_temp_tips_r > span')
for rank, song, length_of_time in zip(ranks, songs, length_of_times):
data = {
'rank': rank.get_text().strip(), # 排名
'singer': song.get_text().split('-')[0].strip(), # 歌手
'name': song.get_text().split('-')[1].strip(), # 歌曲名
'url': song.get('href'), # 歌曲链接
'length_of_time': length_of_time.get_text().strip() # 时长
}
print(data)
return data
if __name__ == '__main__':
get_info('http://www.kugou.com/yy/rank/home/1-8888.html?from=rank')
top500_list = ['http://www.kugou.com/yy/rank/home/{}-8888.html?from=rank'.format(num) for num in range(1, 3)] # range(1, 24)显示所有页
for url in top500_list:
get_info(url)
time.sleep(2)
# 运行结果
# {'rank': '1', 'singer': '展展与罗罗', 'name': '沙漠骆驼', 'url': 'http://www.kugou.com/song/g3d726a.html', 'length_of_time': '5:38'}
# {'rank': '2', 'singer': '黑龙', 'name': '38度6', 'url': 'http://www.kugou.com/song/ootaieb.html', 'length_of_time': '3:11'}
# {'rank': '3', 'singer': '张紫豪', 'name': '可不可以', 'url': 'http://www.kugou.com/song/mkt6v7f.html', 'length_of_time': '4:00'}
# {'rank': '4', 'singer': 'G.E.M.邓紫棋', 'name': '光年之外', 'url': 'http://www.kugou.com/song/eoo8m01.html', 'length_of_time': '3:55'}
# {'rank': '5', 'singer': '半阳', 'name': '流浪', 'url': 'http://www.kugou.com/song/ndiin31.html', 'length_of_time': '3:39'}
zip()函数使用
描述
zip()
函数用于将可迭代对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象。
如果各个可迭代对象的元素个数不一致,则返回的对象长度与最短的可迭代对象相同。
利用 *
号操作符,与zip
相反,进行解压。
语法
zip()
函数语法:
zip(iterable1,iterable2, ...)
参数说明:
-
iterable
-- 一个或多个可迭代对象(字符串、列表、元祖、字典)
返回的是一个对象,如果想要得到列表,可以用 list()
函数进行转换。
实例
>>> a = [1,2,3] #此处可迭代对象为列表
>>> b = [4,5,6]
>>> c = [4,5,6,7,8]
>>> zipped = zip(a,b)
>>> zipped
# #返回的是一个对象
>>> list(zipped)
# [(1, 4), (2, 5), (3, 6)] #使用list()函数转换为列表
>>> list(zip(a,c))
# [(1, 4), (2, 5), (3, 6)]
>>> zipped = zip(a,b)
>>> list(zip(*zipped)) #解压也使用list进行转换
# [(1, 2, 3), (4, 5, 6)]
#v1,v2,v3可是是任何可迭代对象,如:字符串、列表、元祖、字典
v1 = {1:11,2:22} #此处可迭代对象为字典
v2 = {3:33,4:44}
v3 = {5:55,6:66}
v = zip(v1,v2,v3) #压缩
print(list(v))
w = zip(*zip(v1,v2,v3)) #解压
print(list(w))
# [(1, 3, 5), (2, 4, 6)]
# [(1, 2), (3, 4), (5, 6)]
搭配for循环支持并行迭代
list1 = [2,3,4]
list2 = [4,5,6]
for x,y in zip(list1,list2):
print(x,y,'--',x*y)
# 2 4 -- 8
# 3 5 -- 15
# 4 6 -- 24