
本文主要是通过Python爬虫豆瓣音乐TOP250,这是练习爬虫的一个景点案例,里面涵盖了Web请求、HTML、数据处理、数据清洗、数据存储、异常情况处理,非常适合用来做项目和练手,喜欢的话就关注一下。持续分享爬虫技术
可以通过pip命令安装这些库:
pip install requests lxml pandas豆瓣音乐Top250:https://music.douban.com/top250
后面还会涉及到分页
首先进入网站之后打开开发者模式,通过Network确认网页的请求地址,可以看到是一个GET请求

#豆瓣音乐网址
url = "https://music.douban.com/top250"
#设置浏览器头部
header = {
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
}headers 字典中只包含了一个键值对 ‘User-Agent’: ‘…’,这里的 ‘User-Agent’ 是一个非常重要的头部信息,它告诉服务器你的爬虫(或浏览器)的类型和版本。由于很多网站会检查请求的 User-Agent 来判断请求是否来自一个真实的浏览器,因此,在爬虫中设置合适的 User-Agent 是非常重要的,这有助于避免被网站识别为爬虫而拒绝服务(即反爬虫机制)。
代码中,User-Agent 被设置为一个常见的Chrome浏览器的用户代理字符串,这有助于让服务器认为请求是来自一个真实的Chrome浏览器用户。
requests.get(url, headers=headers) 这行代码向指定的URL发送了一个GET请求,并将 headers 字典作为请求的一部分发送给服务器。服务器根据这些头部信息来响应请求。
response.encoding = ‘utf-8’ 这行代码设置了响应内容的编码格式为UTF-8,这是为了确保能够正确地解码响应内容中的文本信息。由于网页内容可能以不同的编码格式发送,因此设置正确的编码格式对于后续处理响应内容非常重要。
response = requests.get(url=url, headers=header).text可以使用print(response)输出一下请求出来的内容,其实就是整个网页的HTML代码,因为太长了,这里就随便放一点
<!DOCTYPE html>
<html lang="zh-CN" class="ua-mac ua-webkit">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="renderer" content="webkit">
<meta name="referrer" content="always">
<meta name="google-site-verification" content="ok0wCgT20tBBgo9_zat2iAcimtN4Ftf5ccsh092Xeyw" />
<title>
豆瓣音乐 Top 250
</title>
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="Sun, 6 Mar 2005 01:00:00 GMT">
<script >var _head_start = new Date();</script>
<script src="https://img1.doubanio.com/f/music/b66ed708717bf0b4a005a4d0113af8843ef3b8ff/js/music/lib/jquery-1.11.0.min.js"></script>
<script src="https://img1.doubanio.com/f/music/3451f4db67b2c8e2f4d8112eee591db9fd236975/js/music/lib/jquery-migrate-1.2.1.js"></script>
<script src="https://img1.doubanio.com/f/shire/4888ee2fda6812f70a064a51c93b84fde8e4a3c2/js/douban.js"></script>
<script src="https://img1.doubanio.com/f/shire/2c0c1c6b83f9a457b0f38c38a32fc43a42ec9bad/js/do.js" data-cfg-autoload="false"></script>
<link href="https://img1.doubanio.com/f/shire/204847ecc7d679de915c283531d14f16cfbee65e/css/douban.css" rel="stylesheet" type="text/css">
<link href="https://img1.doubanio.com/f/music/ea0c776c52fba60e24e92ea802336c1fe9697ff4/css/music/init.css" rel="stylesheet" type="text/css" />
<style type="text/css">
</style>
<link rel="stylesheet" type="text/css" href="https://img1.doubanio.com/f/music/48c72cb4243c8628ce0a8f65a9f474150ce9f0c4/css/music/_init_.css"/>
<!-- COLLECTED CSS -->
<script></script>
<link rel="shortcut icon" href="https://img1.doubanio.com/favicon.ico" type="image/x-icon">
</head>
<body>
<script type="text/javascript">var _body_start = new Date();</script>tree = etree.HTML(response)在网页中可以看到,当前我们只是打开的第一页,但是我们爬取的是所有的信息,所以每一页的内容都要获取,但是每一页的网址是不一样的,所以先要获取每一页的网址

返回/html/body/div[3]/div[1]/div/div[1]/div/div[26]下所有 <a> 标签的href属性值列表。
#获取分页
def page(tree):
tables = tree.xpath('/html/body/div[3]/div[1]/div/div[1]/div/div[26]/a//@href')
print(tables)使用XPath通过tree对象抓取HTML中特定元素的href属性值列表,并将其存储在tables变量中。具体路径为:从/html/body/div3/div1/div/div1/div/div26/a元素获取其@href属性值
输出结果:
['https://music.douban.com/top250?start=25', 'https://music.douban.com/top250?start=50', 'https://music.douban.com/top250?start=75', 'https://music.douban.com/top250?start=100', 'https://music.douban.com/top250?start=125', 'https://music.douban.com/top250?start=150', 'https://music.douban.com/top250?start=175', 'https://music.douban.com/top250?start=200', 'https://music.douban.com/top250?start=225']现在就获取到了每一页的网址
但是通过观察,可以发现每一页的网址是一样的,维度不同的是start=?,第一页是0第二页是25第三页是50,这个规律其实就是步长为25,每一页的数字是相差25的,既然知道这个规律,我们就可以直接推断出每一页的网址
url = ["https://music.douban.com/top250?start={}".format(i) for i in range(0,250,25)]简单输出一下结果看看
['https://music.douban.com/top250?start=0', 'https://music.douban.com/top250?start=25', 'https://music.douban.com/top250?start=50', 'https://music.douban.com/top250?start=75', 'https://music.douban.com/top250?start=100', 'https://music.douban.com/top250?start=125', 'https://music.douban.com/top250?start=150', 'https://music.douban.com/top250?start=175', 'https://music.douban.com/top250?start=200', 'https://music.douban.com/top250?start=225']这样就获取到每一页了,然后只需要循环请求列表中的每一个网址就可以
#设置浏览器头部
header = {
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
}
url = ["https://music.douban.com/top250?start={}".format(i) for i in range(0,250,25)]
#循环请求
for i in url :
# 请求网址
response = requests.get(url=url, headers=header).text
tree = etree.HTML(response)可以看到每一个音乐都是一个table标签,而table标签中的内容都是相同的,

table标签下都有一个tr标签,那我们直接就锁定这个标签

#锁定网页中所有的tr标签,结果就是锁定每一个table标签中的tr,所以需要循环去处理每一个table中的tr
trs = tree.xpath("//tr[@class='item']")
for tr in trs:标题在a标签中,直接去锁定a标签,根据tr的路径来看,标题的内容在tr标签下的第二个td标签中的div标签下的a标签中,我们就按照这个路径来进行定位

for tr in trs:
#获取标题
title = tr.xpath("td[2]/div/a/text()")
print(title)输出看下结果
['\n We Sing. We Dance. We Steal Things.\n ']看到输出的结果中标题前后都有很多空格和换行符
for tr in trs:
#获取标题
title = tr.xpath("normalize-space(td[2]/div/a/text())")
print(title)
#输出结果:We Sing. We Dance. We Steal Things.normalize-space(…):这是一个XPath函数,用于对给定的字符串进行标准化处理,删除字符串前后的空白字符(如空格、换行符等),并将字符串中间的多个连续空白字符替换为一个空格。这对于处理从HTML文档中提取的文本非常有用,因为HTML文档中可能包含许多不必要的空白字符。
歌曲介绍部分实在p标签中,这个p标签跟上面标题的a标签的路径是一致的,而且可以看到文字部分是根据四个 / 分割的,分别是 作者/发行时间/专辑类型/介质/流派 那么我们获取到文字之后就进行拆分,因为这个p标签跟标题的a标签的路径是一样的所以直接使用a标签的定位逻辑就行

现在就获取到了介绍的内容,因为直接输出的话结果会在一个数组中,例如‘Jason Mraz / 2008-05-13 / Import / Audio CD / 民谣’,所以tr.xpath(“td2/div/p/text()”)0,使用下标获取第一个元素,变成字符串,然后对字符串进行拆分
for tr in trs:
#获取标题
title = tr.xpath("normalize-space(td[2]/div/a/text())")
#获取介绍
introduction = tr.xpath("td[2]/div/p/text()")[0]
print(introduction)
#输出结果:Jason Mraz / 2008-05-13 / Import / Audio CD / 民谣根据 / 进行拆分,现在就可以获取到 作者/发行时间/专辑类型/介质/流派 然后存储下来
#获取介绍
introduction = tr.xpath("td[2]/div/p/text()")[0]
#拆分字符串
introduction_ = introduction.split('/')
print(introduction_)
#输出结果 ['Jason Mraz ', ' 2008-05-13 ', ' Import ', ' Audio CD ', ' 民谣']将简介中的信息都存储下来
#拆分字符串
introduction_ = introduction.split('/')
#作者
author = introduction_[0]
#发行时间
release_time = introduction_[1]
#专辑类型
type_album = introduction_[2]
#介质
medium = introduction_[3]
#流派
genre = introduction_[4]在检查过程中发现有的歌曲可能会没有专辑类型,正常拆分出来的字符串应该有5个元素,但是如果没有专辑类型的话,那么就变成四个元素了,那么introduction_4就会报错,超过数组长度,所以要进行判断,如果拆分出来的数组中元素个数小于5个,那么就把专辑类型赋值为空
# 正常来说介绍中应该包含5个内容,但是会存在没有专辑类型,所以判断如果拆分出来的元素个数小于5那么就把专辑类型设置为空
if len(introduction_) <5 :
#作者
author = introduction_[0]
#发行时间
release_time = introduction_[1]
#专辑类型
type_album = ''
#介质
medium = introduction_[2]
#流派
genre = introduction_[3]
else:
# 作者
author = introduction_[0]
# 发行时间
release_time = introduction_[1]
# 专辑类型
type_album = introduction_[2]
# 介质
medium = introduction_[3]
# 流派
genre = introduction_[4]评分所在的路径跟标签还有介绍一样都在同一个div标签下,所以前面路径不用变,把p标签改成div标签,然后是第二个span标签来进行定位
#获取评分
scoring = tr.xpath('td[2]/div/div/span[2]/text()')[0]
print(scoring)
#输出结果 9.1评价人数在评分的下一个span中也就是第三个span标签中

可以看到输出结果中还是存在空格和换行符所以还是要使用normalize-space进行清除
#获取评分人数
scoring_number = tr.xpath('td[2]/div/div/span[3]/text()')
print(scoring_number)
#输出结果: ['\n (\n 116542人评价\n )\n ']使用normalize-space清除空格
#获取评分人数
scoring_number = tr.xpath('normalize-space(td[2]/div/div/span[3]/text())')
print(scoring_number)
# 输出结果 ( 116542人评价 )至此信息就获取完毕了,后面要对信息进行存储
现在已经获取到我们需要的数据,现在就是需要把数据存储到本地,通过Pandas进行存储,但是首先需要把获取到的字段存储到数组中,然后把数组的数据加入到Pandas中
定义一个空数组,然后每次获取完字段数据之后将字段的数据存储进去
#定义一个空数组,用来存储数据
lis = []
#循环请求
for i in url :
# 请求网址
response = requests.get(url=i, headers=header).text
tree = etree.HTML(response)
#锁定网页中所有的tr标签,结果就是锁定每一个table标签中的tr,所以需要循环去处理每一个table中的tr
trs = tree.xpath("//tr[@class='item']")
for tr in trs:
#获取标题
title = tr.xpath("normalize-space(td[2]/div/a/text())")
#获取介绍
introduction = tr.xpath("td[2]/div/p/text()")[0]
#拆分字符串
introduction_ = introduction.split('/')
# 正常来说介绍中应该包含5个内容,但是会存在没有专辑类型,所以判断如果拆分出来的元素个数小于5那么就把专辑类型设置为空
if len(introduction_) <5 :
#作者
author = introduction_[0]
#发行时间
release_time = introduction_[1]
#专辑类型
type_album = ''
#介质
medium = introduction_[2]
#流派
genre = introduction_[3]
else:
# 作者
author = introduction_[0]
# 发行时间
release_time = introduction_[1]
# 专辑类型
type_album = introduction_[2]
# 介质
medium = introduction_[3]
# 流派
genre = introduction_[4]
#获取评分
scoring = tr.xpath('td[2]/div/div/span[2]/text()')[0]
#获取评分人数
scoring_number = tr.xpath('normalize-space(td[2]/div/div/span[3]/text())')
# 将获取到的字段信息存储到数组中
lis.append([title,author,release_time,type_album,medium,genre,scoring,scoring_number])#定义一个空数组,用来存储数据
lis = []
#循环请求
for i in url :
# 请求网址
response = requests.get(url=i, headers=header).text
tree = etree.HTML(response)
#锁定网页中所有的tr标签,结果就是锁定每一个table标签中的tr,所以需要循环去处理每一个table中的tr
trs = tree.xpath("//tr[@class='item']")
for tr in trs:
#获取标题
title = tr.xpath("normalize-space(td[2]/div/a/text())")
#获取介绍
introduction = tr.xpath("td[2]/div/p/text()")[0]
#拆分字符串
introduction_ = introduction.split('/')
# 正常来说介绍中应该包含5个内容,但是会存在没有专辑类型,所以判断如果拆分出来的元素个数小于5那么就把专辑类型设置为空
if len(introduction_) <5 :
#作者
author = introduction_[0]
#发行时间
release_time = introduction_[1]
#专辑类型
type_album = ''
#介质
medium = introduction_[2]
#流派
genre = introduction_[3]
else:
# 作者
author = introduction_[0]
# 发行时间
release_time = introduction_[1]
# 专辑类型
type_album = introduction_[2]
# 介质
medium = introduction_[3]
# 流派
genre = introduction_[4]
#获取评分
scoring = tr.xpath('td[2]/div/div/span[2]/text()')[0]
#获取评分人数
scoring_number = tr.xpath('normalize-space(td[2]/div/div/span[3]/text())')
# 将获取到的字段信息存储到数组中
lis.append([title,author,release_time,type_album,medium,genre,scoring,scoring_number])
#将数组中的数据添加到Pandas中
df = pd.DataFrame(data=lis,columns=['歌曲名','作者','发行时间','专辑类型','介质','流派','评分','评分人数'])df.to_excel('test.xlsx')# 导入requests库,用于发送HTTP请求
import requests
# 导入etree模块,用于解析HTML或XML文档
from lxml import etree
# 导入pandas库,用于数据处理和分析
import pandas as pd
# 设置浏览器头部信息,模拟浏览器访问,防止被网站拒绝服务
header = {
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
}
# 构造豆瓣音乐Top250的URL列表,每页显示25条数据,共10页
url = ["https://music.douban.com/top250?start={}".format(i) for i in range(0, 250, 25)]
# 定义一个空列表,用于存储从网页解析出的数据
lis = []
# 循环遍历URL列表,发送HTTP GET请求
for i in url :
# 发送请求,获取网页内容,并将响应内容(HTML文本)赋值给response变量
response = requests.get(url=i, headers=header).text
# 使用etree的HTML类将HTML文本解析为HTML文档对象,赋值给tree变量
tree = etree.HTML(response)
#锁定网页中所有的tr标签,结果就是锁定每一个table标签中的tr,所以需要循环去处理每一个table中的tr
trs = tree.xpath("//tr[@class='item']")
# 循环遍历每一个tr标签,也就是每一个歌曲
for tr in trs:
#获取标题
title = tr.xpath("normalize-space(td[2]/div/a/text())")
#获取介绍
introduction = tr.xpath("td[2]/div/p/text()")[0]
#拆分字符串
introduction_ = introduction.split('/')
# 正常来说介绍中应该包含5个内容,但是会存在没有专辑类型,所以判断如果拆分出来的元素个数小于5那么就把专辑类型设置为空
if len(introduction_) <5 :
#作者
author = introduction_[0]
#发行时间
release_time = introduction_[1]
#专辑类型
type_album = ''
#介质
medium = introduction_[2]
#流派
genre = introduction_[3]
else:
# 作者
author = introduction_[0]
# 发行时间
release_time = introduction_[1]
# 专辑类型
type_album = introduction_[2]
# 介质
medium = introduction_[3]
# 流派
genre = introduction_[4]
#获取评分
scoring = tr.xpath('td[2]/div/div/span[2]/text()')[0]
#获取评分人数
scoring_number = tr.xpath('normalize-space(td[2]/div/div/span[3]/text())')
# 将获取到的字段信息存储到数组中
lis.append([title,author,release_time,type_album,medium,genre,scoring,scoring_number])
#将数组中的数据添加到Pandas中
df = pd.DataFrame(data=lis,columns=['歌曲名','作者','发行时间','专辑类型','介质','流派','评分','评分人数'])
#写入到本地
df.to_excel('test.xlsx')打开本地文件查看结果

