写在前面的话:每一个实例的代码都会附上相应的代码片或者图片,保证代码完整展示在博客中。最重要的是保证例程的完整性!!!方便自己也方便他人~欢迎大家交流讨论~
Lxml(xpath)
爬虫的抓取方式有好几种,正则表达式,Lxml(xpath)与BeautifulSoup,上一篇讲了用BeautifulSoup来爬取网页图片,这一篇轮到用Lxml(xpath)。继续上实例操作。
xpath简单用法
from lxml import etree
s=etree.HTML(源码) #将源码转化为能被XPath匹配的格式
s.xpath(xpath表达式) #返回为一列表
基础语法
1 // 双斜杠 定位根节点,会对全文进行扫描,在文档中选取所有符合条件的内容,以列表的形式返回。
2 / 单斜杠 寻找当前标签路径的下一层路径标签或者对当前路标签内容进行操作
3/text() 获取当前路径下的文本内容
4/@xxxx 提取当前路径下标签的属性值
5| 可选符 使用|可选取若干个路径 如//p | //div 即在当前路径下选取所有符合条件的p标签和div标签。
6. 点 用来选取当前节点
7.. 双点 选取当前节点的父节点
获取单个页面单条数据
1.获取书名
打开豆瓣读书前250条
豆瓣图书 Top 250 https://book.douban.com/top250
右键审查元素,想我上一篇讲到的一点一点点击下去直到找到标题内容“追风筝的人”,然后对标题的代码(即下图蓝色部分)右键——copy——copy xpath得到标题的xpath
//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[1]/a
开始写代码爬取
from lxml import etree
import requests
url="https://book.douban.com/top250"
respon=requests.get(url)#得到响应
responHtml=respon.text#打印html内容
s=etree.HTML(responHtml)#将源码转化为能被XPath匹配的格式
#在s.xpath()中填入xpath表达式是要去掉多余的tbody标签,否则返回为空列表
bookTitle=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[1]/a')#返回为一列表,列表内容为爬取的标题
print (bookTitle)
运行结果:In[1]中为没有去掉/tbody返回的空列表,In[2]中是去掉之后的返回值
完善步骤1:在xpath表达式中再加入/text() 获取当前路径下的文本内容(见基础语法3)
bookTitle=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[1]/a/text()')
运行结果:
完善步骤2:在xpath表达式中再加入[0]返回列表中的唯一一个元素也就是标题bookTitle=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[1]/a/text()')[0]
运行结果:
2.获取评分和评分人数
按刚刚说的方法得到xpath
#标题的xpath为//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[1]/a
#评分的xpath为//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[2]/span[2]
#评价人数的xpath为//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[2]/span[3]
from lxml import etree
import requests
url="https://book.douban.com/top250"
respon=requests.get(url)#得到响应
responHtml=respon.text#打印html内容
s=etree.HTML(responHtml)#将源码转化为能被XPath匹配的格式
#在s.xpath()中填入xpath表达式是要去掉多余的tbody标签,否则返回为空列表
bookTitle=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[1]/a/text()')[0]#返回为一列表,列表内容为爬取的标题
bookScore=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[2]/span[2]/text()')[0]
bookPeople=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[2]/span[3]/text()')[0]
print (bookTitle,bookScore,bookPeople)
3.获取书的路径和图片地址
/@xxxx 提取当前路径下标签的属性值(见基础语法4),在第一图书的标题所在的标签‘a’下出现href=……提供了书的链接,因此在书的标题xpath后加@href
同理找到该书对应图片的xpath,在xpath后@src
新增代码
bookHref=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[1]/a/@href')[0]
bookImg=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[1]/a/img/@src')[0]
得到的完整代码
#豆瓣图书 Top 250 https://book.douban.com/top250
#标题的xpath为//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[1]/a
#评分的xpath为//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[2]/span[2]
#评价人数的xpath为//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[2]/span[3]
#图片的xpath为//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[1]/a/img
from lxml import etree
import requests
url="https://book.douban.com/top250"
respon=requests.get(url)#得到响应
responHtml=respon.text#打印html内容
s=etree.HTML(responHtml)#将源码转化为能被XPath匹配的格式
#在s.xpath()中填入xpath表达式是要去掉多余的tbody标签,否则返回为空列表
bookHref=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[1]/a/@href')[0]
bookTitle=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[1]/a/text()')[0]#返回为一列表,列表内容为爬取的标题
bookScore=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[2]/span[2]/text()')[0]
bookPeople=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[2]/span[3]/text()')[0]
bookImg=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[1]/a/img/@src')[0]
print (bookHref,bookTitle,bookScore,bookPeople,bookImg)
注意!!!一定要去掉/tbody否则会出现下图的错误
获取单个页面多条数据
让我在新建一个python文件写它3.py,获取多本书的标题,跟刚才的操作一样
from lxml import etree
import requests
url="https://book.douban.com/top250"
respon=requests.get(url)#得到响应
responHtml=respon.text#打印html内容
s=etree.HTML(responHtml)#将源码转化为能被XPath匹配的格式
#在s.xpath()中填入xpath表达式是要去掉多余的tbody标签,否则返回为空列表
bookTitle1=s.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[1]/a/text()')[0]#返回为一列表,列表内容为爬取的标题
bookTitle2=s.xpath('//*[@id="content"]/div/div[1]/div/table[2]/tr/td[2]/div[1]/a/text()')[0]
bookTitle3=s.xpath('//*[@id="content"]/div/div[1]/div/table[3]/tr/td[2]/div[1]/a/text()')[0]
bookTitle4=s.xpath('//*[@id="content"]/div/div[1]/div/table[4]/tr/td[2]/div[1]/a/text()')[0]
print (bookTitle1,bookTitle2,bookTitle3,bookTitle4)
发现代码中每个title都要复制xpath总归是太麻烦了,观察标题xpath的特点,发现只有table序号不一样,那就去掉序号,我们来缩减一下
from lxml import etree
import requests
url="https://book.douban.com/top250"
respon=requests.get(url)#得到响应
responHtml=respon.text#打印html内容
s=etree.HTML(responHtml)#将源码转化为能被XPath匹配的格式
#在s.xpath()中填入xpath表达式是要去掉多余的tbody标签,否则返回为空列表
bookTitles=s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[1]/a/text()')#返回为一列表,列表内容为爬取的标题
for bookTitle in bookTitles:
print(bookTitle.strip())
改代码只会打印到本页面最底的最后一本书的标题,不会自动翻页哦
但是从上图你发现整个标题列表有空元素的存在,len(bookTitles)=29,但是每页只有25条数据,需要去掉空元素,添加下列代码
newbookTitle=[]
for bookTitle in bookTitles:
newbookTitle.append(bookTitle.strip())if bookTitle.strip() else None
完整代码为
from lxml import etree
import requests
url="https://book.douban.com/top250?start=0"
respon=requests.get(url)#得到响应
responHtml=respon.text#打印html内容
s=etree.HTML(responHtml)#将源码转化为能被XPath匹配的格式
#在s.xpath()中填入xpath表达式是要去掉多余的tbody标签,否则返回为空列表
bookTitles=s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[1]/a/text()')#返回为一列表,列表内容为爬取的标题
print(type(bookTitles[7]))#第8个位空,查看空元素的类型
newbookTitle=[]#创建新列表
for bookTitle in bookTitles:
#把非空元素加进新列表中
newbookTitle.append(bookTitle.strip())if bookTitle.strip() else None
length=len(newbookTitle)
print(type(bookTitles))
print(length)#测试新列表长度
for bookTitle in newbookTitle:
print(bookTitle.strip())
然后你发现空元素都去掉了,接下来进行下一步!
其它的信息如:链接地址,评分,评价人数都可以用同样的办法来获取,现在同时获取多条数据,因为每页数据有多少条就打印多少条
from lxml import etree
import requests
url="https://book.douban.com/top250"
respon=requests.get(url)#得到响应
responHtml=respon.text#打印html内容
s=etree.HTML(responHtml)#将源码转化为能被XPath匹配的格式
#在s.xpath()中填入xpath表达式是要去掉多余的tbody标签,否则返回为空列表
bookHref=s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[1]/a/@href')
bookTitle=s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[1]/a/text()')#返回为一列表,列表内容为爬取的标题
bookScore=s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[2]/span[2]/text()')
bookPeople=s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[2]/span[3]/text()')
bookImg=s.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[1]/a/img/@src')
#只有bookTitles中会爬取到空,因此去掉bookTitles中的空
newbookTitle=[]
for bookTitle in bookTitles:
newbookTitle.append(bookTitle.strip())if bookTitle.strip() else None
length=len(bookHref)#计算这个页面总共有多少条数据
print(len(bookHref),len(newbookTitle),len(bookScore),len(bookPeople),len(bookImg))
for i in range(length):
print (bookHref[i],newbookTitle[i],bookScore[i],bookPeople[i],bookImg[i])
好了!顺利完成该页多条数据的爬取任务!
接下来精简xpath路径,毕竟大家的xpath前面都是一模一样的
from lxml import etree
import requests
url="https://book.douban.com/top250"
respon=requests.get(url)#得到响应
responHtml=respon.text#打印html内容
s=etree.HTML(responHtml)#将源码转化为能被XPath匹配的格式
trs=s.xpath('//*[@id="content"]/div/div[1]/div/table/tr')#提取相同的前缀
#注意新节点是tr下的节点,每一个tr就是一个数据条,代表关于一本书的全部相关内容
for tr in trs:
#此时bookHref只是某个tr(即某本书)的链接,和求单条数据的情况一样,返回的bookHref列表中只有一个元素即该书的链接,因此后面加[0]取该元素
bookHref=tr.xpath('./td[2]/div[1]/a/@href')[0]
bookTitle=tr.xpath('./td[2]/div[1]/a/text()')[0]
bookScore=tr.xpath('./td[2]/div[2]/span[2]/text()')[0]
bookPeople=tr.xpath('./td[2]/div[2]/span[3]/text()')[0]
bookImg=tr.xpath('./td[1]/a/img/@src')[0]
print (bookHref,bookTitle,bookScore,bookPeople,bookImg)
运行结果:因为数据太多了只放该页面第一本书和最后一本书的爬取结果
获取多个页面多条数据
首先,翻页一下观察翻页网址
https://book.douban.com/top250?start=0
https://book.douban.com/top250?start=25
https://book.douban.com/top250?start=50
……
写一个翻页的代码
for i in range(10):
url = 'https://music.douban.com/top250?start={}'.format(i*25)
print url
整理后的完整代码
from lxml import etree
import requests
#得到每页网址并爬取数据
def getUrl():
for i in range(10)
url = 'https://book.douban.com/top250?start={}'.format(i*25)
print("开始爬取第",i+1,"页")
scrapyPage(url)#调用爬取网页数据的函数
#爬取网页数据
def scrapyPage(url):
respon=requests.get(url)#得到响应
responHtml=respon.text#打印html内容
s=etree.HTML(responHtml)#将源码转化为能被XPath匹配的格式
trs=s.xpath('//*[@id="content"]/div/div[1]/div/table/tr')#提取相同的前缀
for tr in trs:
bookHref=tr.xpath('./td[2]/div[1]/a/@href')[0]
bookTitle=tr.xpath('./td[2]/div[1]/a/text()')[0]
bookScore=tr.xpath('./td[2]/div[2]/span[2]/text()')[0]
bookPeople=tr.xpath('./td[2]/div[2]/span[3]/text()')[0]
bookImg=tr.xpath('./td[1]/a/img/@src')[0]
print (bookHref,bookTitle,bookScore,bookPeople,bookImg)
if '__main__':
getUrl()
运行结果:对比一下最后一页最后一本书
有坑处请注意
注意!!!使用xpath有一个容易出的错误就是有的内容可能为空,它爬不到就会报IndexError: list index out of range的错误
这个错误是因为我在url = 'https://book.douban.com/top250?start={}'.format(i*25)
网址的时候不小心把book写成了music的网址,就是豆瓣音乐TOP250的网址时发现的执行时bookStore和bookPeople均出现了这个错误
改成如下所示就不会报错了,只不过爬的是豆瓣音乐TOP250的数据,我最后才发现是网址写错了
bookscore=tr.xpath('./td[2]/div[2]/span[2]/text()')[0]
bookScore=bookscore[0]if bookscore else None
bookpeople=tr.xpath('./td[2]/div[2]/span[3]/text()')[0]
bookPeople=bookpeople[0]if bookpeople else None
综上,虽然爬取豆瓣图书TOP250没有出现爬到的为空的现象,但是如果以后用xpath再遇到IndexError: list index out of range的报错,就可以考虑是不是你要爬的数据有一部分为空了,然后补充一个处理该异常的代码块
附: Python format 格式化函数 http://www.runoob.com/python/att-string-format.html