爬虫的抓取方式有好几种, 正则表达式, Lxml(xpath) 与 Beautiful, 我在网上查了一下资料, 了解到三者之间的使用难度与性能
三种爬虫方式的对比
抓取方式 | 性能 | 使用难度 |
---|---|---|
正则表达式 | 快 | 困难 |
Lxml | 快 | 简单 |
BeautifulSoup | 慢 | 简单 |
这样一比较我我选择了 Lxml(xpath) 的方式了, 虽然有三种方式, 但肯定是要选择最好的方式来爬虫, 这个道理大家都懂, 另外有兴趣的朋友也可以去了解另外两种爬虫方式!
好了现在来讲讲 xpath
由于 Xpath 属于 lxml 模块, 所以首先需要安装 lxml 库, 老办法直接在 file-->setting---project interpreter 一键添加 lxml 库
xpath 简单用法
from lxml import etree
s=etree.html(源码) #将源码转化为能被 XPath 匹配的格式
s.xpath(xpath 表达式) #返回为一列表,
基础语法:
// 双斜杠 定位根节点, 会对全文进行扫描, 在文档中选取所有符合条件的内容, 以列表的形式返回
/ 单斜杠 寻找当前标签路径的下一层路径标签或者对当前路标签内容进行操作
/text() 获取当前路径下的文本内容
/@xxxx 提取当前路径下标签的属性值
| 可选符 使用 | 可选取若干个路径 如 //p | //div 即在当前路径下选取所有符合条件的 p 标签和 div 标签
. 点 用来选取当前节点
.. 双点 选取当前节点的父节点
学以致用, 方能让我们能快速掌握 xpath 语法功能
我们这次需要爬取豆瓣音乐前 250 条
打开豆瓣音乐: https://music.douban.com/top250
获取单条数据
1. 获取音乐标题
右键弹出菜单栏 Copy==> Copy Xpath
这里我们想获取音乐标题, 音乐标题的 xpath 是:
- xpath://*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div/a
- # coding:utf-8
- 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 后, 是这样的,
title = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a)
然后我们再运行代码
得到:
<Element a at 0x53d26c8>
说明标题被获取到了
因为要获取标题文本, 所以 xpath 表达式要追加 / text()
title = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a/text())# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text()
又因为这个 s.xpath 返回的是一个集合, 且集合中只有一个元素所以我再追加一个 [0]
新的表达式:
title = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a/text())[0]# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text(), 再追加 [0]
重新运行得到结果:
We Sing. We Dance. We Steal Things.
正是我们想要的标题
2. 获取音乐评分与评价人数
老办法, 先用右键 copy 评分的 xpath :
//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div/div/span[2]
复制评价人数的 xpath:
//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div/div/span[3]/text()
同样的我们要把 tbody 去掉, 然后重新运行代码:
- # coding:utf-8
- 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]# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text()
- score = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[2]/text())[0]# 因为要获取文本, 所以我需要这个当前路径下的文本, 所以使用 / text()
- numbers = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[3]/text())[0]# 因为要获取文本, 所以我需要这个当前路径下的文本, 所以使用 / text()
- print title,score,numbers
得到:
- We Sing. We Dance. We Steal Things.
- 9.1
- (
100395 人评价
)
3. 获取音乐链接
copy 标题的 xpath,:
//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div/a
想获取音乐连接 href 这里需要, 获取这个标签属于,/@xxx 可以提取当前路径标签下的属性值
//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div/a/@href
代码:
- # coding:utf-8
- 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]# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text()
- title = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a/text())[0]# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text()
- score = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[2]/text())[0]# 因为要获取文本, 所以我需要这个当前路径下的文本, 所以使用 / text()
- numbers = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[3]/text())[0]# 因为要获取文本, 所以我需要这个当前路径下的文本, 所以使用 / text()
- print href,title,score,numbers
运行代码得到:
- https://music.douban.com/subject/2995812/
- We Sing. We Dance. We Steal Things.
- 9.1
- (
100395 人评价
)
5. 获取图片地址:
找到图片, 复制他的 xpath 地址:
//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[1]/a/img
运行代码:
- # coding:utf-8
- 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]# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text()
- title = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/a/text())[0]# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text()
- score = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[2]/text())[0]# 因为要获取文本, 所以我需要这个当前路径下的文本, 所以使用 / text()
- numbers = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div/div/span[3]/text())[0]# 因为要获取文本, 所以我需要这个当前路径下的文本, 所以使用 / text()
- imgpath = s.xpath(//*[@id="content"]/div/div[1]/div/table[1]/tr/td[1]/a/img/@src)[0]# 因为要获取文本, 所以我需要这个当前路径下的文本, 所以使用 / text()
- print href,title,score,numbers,imgpath
老套路:
得到结果:
- https://music.douban.com/subject/2995812/
- We Sing. We Dance. We Steal Things.
- 9.1
- (
100395 人评价
- ) https: //img3.doubanio.com/spic/s2967252.jpg
但是这只是获取了一条数据, 如果获取多条数据呢?
获取多条数据
我们再看第二条数据, 第三条数据, 第四条数据
得到他们的 xpath:
- # coding:utf-8
- 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]# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text()
- title2 = s.xpath(//*[@id="content"]/div/div[1]/div/table[2]/tr/td[2]/div/a/text())[0]# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text()
- title3 = s.xpath(//*[@id="content"]/div/div[1]/div/table[3]/tr/td[2]/div/a/text())[0]# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text()
- title4 = s.xpath(//*[@id="content"]/div/div[1]/div/table[4]/tr/td[2]/div/a/text())[0]# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / text()
- print title,title2,title3,title4
得到:
- We Sing. We Dance. We Steal Things.
- Viva La Vida
华丽的冒险
范特西
对比他们的 xpath, 发现只有 table 序号不一样, 我们可以就去掉序号, 得到通用的 xpath 信息:
运行代码:
- # coding:utf-8
- 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())# 因为要获取标题, 所以我需要这个当前路径下的文本, 所以使用 / 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 条, 所以:
完整代码如下:
- # coding:utf-8
- 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 代码运行., 注意你得装上 lxml 与 requests 库.
我们也发现了问题每一个 xpath 路径特别长, 能不能精简一下呢?
- #####5.精简一下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/tr
那我能不能把这些东西提出来呢, 让后面的不同的自己去追加, 另外这样写也不用管每个页面到底有多少条数据, 只管查就行了所以代码做了一下精简
- 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
得到的结果和之前是一样的
但是, 但是, 这只是一个页面的数据, 我现在想爬取多个页面的数据, 怎么办呢?
获取个多页面数据.
观察一下翻页路径:
- 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
正是自己要的结果
好了最后我们把代码拼装在一起, 并注意每个方法的用途
完整代码
- # coding:utf-8
- from lxml import etree
- import requests
- # 获取页面地址
- 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]
- 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
- if __main__:
- getUrl()
来源: http://www.bubuko.com/infodetail-2492453.html