首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Python爬虫实战:豆瓣TOP250,从底层到代码的超详细讲解,新手看完必会!

Python爬虫实战:豆瓣TOP250,从底层到代码的超详细讲解,新手看完必会!

作者头像
小白的大数据之旅
发布2024-11-20 18:45:05
发布2024-11-20 18:45:05
1.6K0
举报

本文主要是通过Python爬虫豆瓣音乐TOP250,这是练习爬虫的一个景点案例,里面涵盖了Web请求、HTML、数据处理、数据清洗、数据存储、异常情况处理,非常适合用来做项目和练手,喜欢的话就关注一下。持续分享爬虫技术

准备工作

安装必要的库:

  • requests:用于发送HTTP请求。
  • lxml:用于解析HTML页面。
  • pandas:用于数据存储和处理,特别是将爬取的数据保存到CSV文件中。

可以通过pip命令安装这些库:

代码语言:javascript
复制
pip install requests lxml pandas

确定目标网站:

豆瓣音乐Top250:https://music.douban.com/top250

后面还会涉及到分页

准备反爬机制

防爬技巧:从入门到精通,保障爬虫稳定运行

分析网站

首先进入网站之后打开开发者模式,通过Network确认网页的请求地址,可以看到是一个GET请求

代码步骤

定义网页链接和请求头部

代码语言:javascript
复制
#豆瓣音乐网址
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,这是为了确保能够正确地解码响应内容中的文本信息。由于网页内容可能以不同的编码格式发送,因此设置正确的编码格式对于后续处理响应内容非常重要。

打开网页并解析

请求网页内容
代码语言:javascript
复制
response = requests.get(url=url, headers=header).text
  • requests.get(url=url, headers=header):这是requests库的一个函数,用于向指定的url发送GET请求。url参数是你想要请求的资源的URL地址。headers参数是一个字典,包含了请求头信息,这些信息可以模拟浏览器请求,帮助绕过一些简单的反爬虫机制,比如指定User-Agent来表明你的请求来自于哪个浏览器或设备。
  • .text:这个属性获取了响应的文本内容,即HTML页面的源代码。requests.get()函数返回的是一个Response对象,这个对象包含了从服务器返回的所有信息,如状态码、响应头、响应体等。通过.text属性,我们可以获取到HTML页面的原始文本内容。

可以使用print(response)输出一下请求出来的内容,其实就是整个网页的HTML代码,因为太长了,这里就随便放一点

代码语言:javascript
复制
<!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>
解析HTML内容:
代码语言:javascript
复制
tree = etree.HTML(response)
  • etree.HTML(response):这是lxml库中的etree模块的一个函数,用于将字符串形式的HTML内容解析为HTML文档树(DOM树)。这个树形结构表示了HTML页面的所有元素和它们之间的关系,便于我们进行后续的查找、修改等操作。这里的response就是上一步通过requests.get()获取的HTML页面的文本内容。
  • tree:这个变量现在引用了通过etree.HTML()函数解析得到的HTML文档树。通过这个树形结构,我们可以使用lxml提供的各种查找和修改DOM的方法,比如使用XPath或CSS选择器来定位特定的HTML元素。

关于分页

在网页中可以看到,当前我们只是打开的第一页,但是我们爬取的是所有的信息,所以每一页的内容都要获取,但是每一页的网址是不一样的,所以先要获取每一页的网址

获取所有页面的网址

返回/html/body/div[3]/div[1]/div/div[1]/div/div[26]下所有 <a> 标签的href属性值列表。

代码语言:javascript
复制
#获取分页
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属性值

输出结果:

代码语言:javascript
复制
['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的,既然知道这个规律,我们就可以直接推断出每一页的网址

列表推导式获取每页网址
代码语言:javascript
复制
url = ["https://music.douban.com/top250?start={}".format(i) for i in range(0,250,25)]
  • https://music.douban.com/top250?start={} 是豆瓣音乐Top 250的基础URL,其中{}是一个占位符,用于后续通过.format(i)方法填充start参数的值。
  • range(0, 250, 25) 生成一个从0开始到250(不包括250)的序列,步长为25。这意味着序列中的数字将是0, 25, 50, …, 225。这个序列的每个数字代表了一个分页的起始点,豆瓣音乐Top 250每页展示一定数量的条目(可能是25条),start参数就是用来指定从哪一条条目开始展示当前页的。

简单输出一下结果看看

代码语言:javascript
复制
['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']

这样就获取到每一页了,然后只需要循环请求列表中的每一个网址就可以

代码语言:javascript
复制
#设置浏览器头部
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标签,那我们直接就锁定这个标签

代码语言:javascript
复制
#锁定网页中所有的tr标签,结果就是锁定每一个table标签中的tr,所以需要循环去处理每一个table中的tr
trs = tree.xpath("//tr[@class='item']")
for tr in trs:
获取标题

标题在a标签中,直接去锁定a标签,根据tr的路径来看,标题的内容在tr标签下的第二个td标签中的div标签下的a标签中,我们就按照这个路径来进行定位

代码语言:javascript
复制
for tr in trs:
    #获取标题
    title = tr.xpath("td[2]/div/a/text()")
    print(title)

输出看下结果

代码语言:javascript
复制
['\n            We Sing. We Dance. We Steal Things.\n       ']

看到输出的结果中标题前后都有很多空格和换行符

使用normalize-space来消除
代码语言:javascript
复制
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,使用下标获取第一个元素,变成字符串,然后对字符串进行拆分

代码语言:javascript
复制
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 / 民谣
拆分字符串

根据 / 进行拆分,现在就可以获取到 作者/发行时间/专辑类型/介质/流派 然后存储下来

代码语言:javascript
复制
#获取介绍
introduction = tr.xpath("td[2]/div/p/text()")[0]
#拆分字符串
introduction_ = introduction.split('/')
print(introduction_)
#输出结果 ['Jason Mraz ', ' 2008-05-13 ', ' Import ', ' Audio CD ', ' 民谣']
变量赋值

将简介中的信息都存储下来

代码语言:javascript
复制
#拆分字符串
introduction_ = introduction.split('/')
#作者
author = introduction_[0]
#发行时间
release_time = introduction_[1]
#专辑类型
type_album = introduction_[2]
#介质
medium = introduction_[3]
#流派
genre = introduction_[4]
没有专辑类型的问题

在检查过程中发现有的歌曲可能会没有专辑类型,正常拆分出来的字符串应该有5个元素,但是如果没有专辑类型的话,那么就变成四个元素了,那么introduction_4就会报错,超过数组长度,所以要进行判断,如果拆分出来的数组中元素个数小于5个,那么就把专辑类型赋值为空

代码语言:javascript
复制
# 正常来说介绍中应该包含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标签来进行定位

代码语言:javascript
复制
#获取评分
scoring = tr.xpath('td[2]/div/div/span[2]/text()')[0]
print(scoring)
#输出结果 9.1
获取评价人数

评价人数在评分的下一个span中也就是第三个span标签中

可以看到输出结果中还是存在空格和换行符所以还是要使用normalize-space进行清除

代码语言:javascript
复制
#获取评分人数
scoring_number = tr.xpath('td[2]/div/div/span[3]/text()')
print(scoring_number)
#输出结果: ['\n                    (\n                            116542人评价\n                    )\n                ']

使用normalize-space清除空格

代码语言:javascript
复制
#获取评分人数
scoring_number = tr.xpath('normalize-space(td[2]/div/div/span[3]/text())')
print(scoring_number)
# 输出结果 ( 116542人评价 )

至此信息就获取完毕了,后面要对信息进行存储

存储数据

现在已经获取到我们需要的数据,现在就是需要把数据存储到本地,通过Pandas进行存储,但是首先需要把获取到的字段存储到数组中,然后把数组的数据加入到Pandas中

定义数组存储数据

定义一个空数组,然后每次获取完字段数据之后将字段的数据存储进去

代码语言:javascript
复制
#定义一个空数组,用来存储数据
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中
代码语言:javascript
复制
#定义一个空数组,用来存储数据
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=['歌曲名','作者','发行时间','专辑类型','介质','流派','评分','评分人数'])
pandas写入到本地
代码语言:javascript
复制
df.to_excel('test.xlsx')

完整源码

代码语言:javascript
复制
# 导入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')

打开本地文件查看结果

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 准备工作
    • 安装必要的库:
    • 确定目标网站:
    • 准备反爬机制
    • 分析网站
  • 代码步骤
    • 定义网页链接和请求头部
    • 打开网页并解析
      • 请求网页内容
      • 解析HTML内容:
    • 关于分页
    • 获取所有页面的网址
      • 列表推导式获取每页网址
    • 获取每个音乐的信息
      • 获取标题
      • 获取介绍
      • 获取评分
      • 获取评价人数
    • 存储数据
      • 定义数组存储数据
      • 将数组添加到Pandas中
      • pandas写入到本地
  • 完整源码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档