现学活用xpath爬取豆瓣音乐

来自:googpy(微信号:googpy),作者:stormwen

阅读文本大概需要 5 分钟。

前两篇主要给大家介绍了xpath的基础知识、以及xpath的常用操作,按照计划,今天是xpath的最后一篇文章,给大家介绍一个用xpath爬取豆瓣音乐的实战项目。学以致用,方能让我们快速掌握xpath语法功能。


爬取目标


本次我们需要爬取豆瓣音乐前250条,打开豆瓣音乐:https://music.douban.com/top250


爬取的内容有:

  • 音乐标题

  • 音乐评分与评价人数

  • 音乐链接

  • 图片地址


下面就让我们根据任务要求,一步一步的来编写代码,最后再将代码整合,实现功能。


1.获取音乐标题


打开网址,按下F12,然后查找标题,右键弹出菜单栏Copy==>Copy Xpath



这里就是我们想获取音乐标题的xpath://*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div/a,具体实现爬取音乐标题的代码如下:


from lxml import etree
import requests

url = 'https://music.douban.com/top250'

html = requests.get(url).text ?
s = etree.HTML(html)
title = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div/a')
print(title)


运行代码,结果居然是空的。这是为什么呢?


这里需要注意,浏览器复制的xpath只能作参考,浏览器经常会在自己里面增加多余的tbody标签,我们需要手动把这个标签(/tbody)删掉,然后再运行代码,结果如下:


[,?]


此时,说明标题被获取到了,因为要获取标题文本,所以xpath表达式要追加/text(),此外,这个s.path返回的是一个集合,且集合中只有一个元素所以我再追加一个[0],新的表达式为


title?=?s.xpath('//[@id="content"]/div/div[1]/div/table/tr/td[2]/div/a/text()')[0]


再次运行代码得到结果:

We Sing. We Dance. We Steal Things.

正是我们想要的标题。


2.获取音乐评分与评价人数


和1中获取xpath的方法一样,此时的xpath为://*[@id="content"]/div/div[1]/div/table[1] /tr/td[2]/div/div/span[3]/text()。具体代码如下:


from lxml import etree
import requests

url = 'https://music.douban.com/top250'

html = requests.get(url).text
s = etree.HTML(html)
title = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a/text()')[0]
score = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[2]/text()')[0]
numbers = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[3]/text()')[0]
print(title,score,numbers)


运行结果:


We Sing. We Dance. We Steal Things.
? ? ? ?9.1
? ? ? ? ? ? ? ? ? ?(
? ? ? ? ? ? ? ? ? ? ? ? ? ?106711人评价
? ? ? ? ? ? ? ? ? ?)


3.获取音乐链接


先Copy标题的xpath://*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div/a,想获取音乐链接href这里需要在xpath末尾加入/@href标签,就可以提取当前路径标签下的属性值。

代码如下:


from lxml import etree
import requests

url = 'https://music.douban.com/top250'

html = requests.get(url).text
s = etree.HTML(html)
href = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a/@href')[0]
title = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a/text()')[0]
score = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[2]/text()')[0]
numbers = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[3]/text()')[0]
print(href,title,score,numbers)


运行结果:


https://music.douban.com/subject/2995812/ 
? ? ? ? ? ?We Sing. We Dance. We Steal Things.
? ? ? ?9.1
? ? ? ? ? ? ? ? ? ?(
? ? ? ? ? ? ? ? ? ? ? ? ? ?106711人评价
? ? ? ? ? ? ? ? ? ?)


4.获取图片地址


找到图片,复制他的xpath地址://*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[1]/a/img,还是按照老套路,代码如下:


from lxml import etree
import requests

url = 'https://music.douban.com/top250'

html = requests.get(url).text
s = etree.HTML(html)
href = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a/@href')[0]
title = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a/text()')[0]
score = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[2]/text()')[0]
numbers = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[3]/text()')[0]
imgpath = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[1]/a/img/@src')[0]
print(href,title,score,numbers,imgpath)


运行结果:


https://music.douban.com/subject/2995812/ 
? ? ? ? ? ?We Sing. We Dance. We Steal Things.
? ? ? ?9.1
? ? ? ? ? ? ? ? ? ?(
? ? ? ? ? ? ? ? ? ? ? ? ? ?106711人评价
? ? ? ? ? ? ? ? ? ?)
? ? ? ? ? ? ? ? https://img3.doubanio.com/view/subject/s/public/s2967252.jpg


到这里,代码已经可以实现基本功能,但只能获取一条数据,如果想获取多条数据呢?


5.获取多条数据



我们copy第二条数据、第三条数据、第四条数据的xpath


title = s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a/text()')[0]
title2 = s.xpath('//*[@id="content"]/div/div[1]/div/table[2]/tr/td[2]/div/a/text()')[0]
title3 = s.xpath('//*[@id="content"]/div/div[1]/div/table[3]/tr/td[2]/div/a/text()')[0]
title4 = s.xpath('//*[@id="content"]/div/div[1]/div/table[4]/tr/td[2]/div/a/text()')[0]


对比它们的xpath,可以发现只有table序号不一样,所以我们可以去掉序号,就可以得到通用的xpath信息,具体代码如下:


from lxml import etree
import requests

url = 'https://music.douban.com/top250'

html = requests.get(url).text
s = etree.HTML(html)
titles = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div/a/text()')

for title in titles:
? ?print(title.strip())


运行结果:


We Sing. We Dance. We Steal Things.
Viva La Vida
华丽的冒险
范特西
後。青春期的诗
是时候
Lenka
Start from Here
旅行的意义
太阳
Once (Soundtrack)
Not Going Anywhere
American Idiot
OK
无与伦比的美丽
亲爱的...我还不知道
城市
O
Wake Me Up When September Ends
叶惠美
七里香
21
My Life Will...
寓言
你在烦恼什么


同理,我们可以获取其他信息:链接地址、评分、评价人数以及照片,现在我们可以同时获取多条数据了。每页数据是25条,完整代码如下:


from lxml import etree
import requests
url = 'https://music.douban.com/top250'
html = requests.get(url).text
s = etree.HTML(html)
hrefs = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div/a/@href')
titles = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div/a/text()')
scores = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div/div/span[2]/text()')
numbers = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div/div/span[3]/text()')
imgs = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[1]/a/img/@src')
for i in range(25):
? ?print(hrefs[i],titles[i],scores[i],numbers[i],imgs[i])


运行代码,可以得到一大批结果,我就不展示了,有兴趣可以直接copy代码运行。


到这里,我们发现了一个问题,每一个xpath路径特别长,能不能精简一下代码呢?


6.精简xpath路径


hrefs = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div/a/@href')
titles = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div/a/text()')
scores = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div/div/span[2]/text()')
numbers = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div/div/span[3]/text()')
imgs = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[1]/a/img/@src')


观察发现获取几个信息的xpath前缀都是//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr,那我们能不能把这些东西提取出来,让后面的不同部分自己追加呢?当然是可以的,精简代码如下:


from lxml import etree
import requests

url = 'https://music.douban.com/top250'

html = requests.get(url).text
s = etree.HTML(html)
trs = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr') ?#先提取tr之前的节点集合

for tr in trs: #遍历tr
? ?href = tr.xpath('./td[2]/div/a/@href')[0] ?#注意新节点是tr下的节点
? ?title = tr.xpath('./td[2]/div/a/text()')[0]
? ?score = tr.xpath('./td[2]/div/div/span[2]/text()')[0]
? ?number = tr.xpath('./td[2]/div/div/span[3]/text()')[0]
? ?img = tr.xpath('./td[1]/a/img/@src')[0]
? ?print(href,title,score,number,img)


得到的结果和之前是一样的,另外这样写不用关每个页面到底有多少条数据,只管查就行了。


但是,到了这里,代码也只是爬取了一个页面的数据,我们现在想爬取多个页面的数据,该怎么办呢?


7.获取多页面数据


观察一下翻页路径:


https://music.douban.com/top250?start=0
https://music.douban.com/top250?start=25
https://music.douban.com/top250?start=50


我们可以发现页面只是后面start参数发生了改变,且增长为每次25,并且250条数据正好是10页,所以,我们可以遍历页面,代码如下:


for i in range(10):
url = 'https://music.douban.com/top250?start={}'.format(i*25)
print(url)


运行结果:


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


这正是我们要的结果,最后我们把代码整合在一起。


8.整合代码


from lxml import etree
import requests
import json
import re

# 获取页面地址
def getUrl():
? ?for i in range(10):
? ? ? ?url = 'https://music.douban.com/top250?start={}'.format(i*25)
? ? ? ?scrapyPage(url)

#爬取每页数据
def scrapyPage(url):
? ?html = requests.get(url).text
? ?s = etree.HTML(html)
? ?trs = s.xpath('//*[@id="content"]/div/div[1]/div/table/tr')

? ?for tr in trs:
? ? ? ? href = tr.xpath('./td[2]/div/a/@href')[0]
? ? ? ? title = tr.xpath('./td[2]/div/a/text()')[0].strip()
? ? ? ? score = tr.xpath('./td[2]/div/div/span[2]/text()')[0].strip()
? ? ? ? numbers = tr.xpath('./td[2]/div/div/span[3]/text()')[0].strip().replace(" ","").replace("\n","")
? ? ? ? img = tr.xpath('./td[1]/a/img/@src')[0].strip()
? ? ? ? items = [href,title,score,numbers,img]
? ? ? ? with open('temp.txt', 'a', encoding='utf-8') as f:
? ? ? ? ? ? f.write(json.dumps(items, ensure_ascii=False) + '\n')

if '__main__':
? ?getUrl()


运行代码,在同一个目录下生成了temp.txt,里面保存的就是爬取的信息。



总结


今天给大家介绍了一个用xpath爬取豆瓣音乐的实战项目,爬取了音乐标题、评分和评价人数、链接以及图片。我们先实现爬取单条信息代码,再实现爬取单页面信息,最后实现爬取豆瓣音乐前250条信息,每一步都环环相扣,联系紧密,对大家熟练掌握xpath很有帮助。

推荐↓↓↓
Python编程
上一篇:自学Python6个月后,我发现学Python必看这三本书,让你少走一半弯路! 下一篇:学爬虫利器Xpath,看这一篇就够了