python爬虫实践——零基础快速入门(三)爬取豆瓣图书

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MTbaby/article/details/79174036

上一篇文章讲的是python爬虫实践——零基础快速入门(二)爬取豆瓣电影,爬取豆瓣电影一页的信息。那想要爬取多个网页信息呢?那写代码就有点不够了。

下面我们来爬取豆瓣TOP250图书信息,地址如下:

https://book.douban.com/top250

我们要爬取哪些信息呢?如下图:

这里写图片描述

1.检查并复制《追风筝的人》书名的xpath如下:

这里写图片描述

//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[1]/a

我们按照同样套路来尝试一下:

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

url = 'https://book.douban.com/top250'
data = requests.get(url).text
f = etree.HTML(data)
books = f.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[1]/a/@title')

我靠,什么情况,居然返回是空值???

注意:浏览器复制的 xpath 信息并不是完全 可靠的,浏览器经常会自己在里面增加多余的 tbody 标签,我们需要手动把这些标签删掉。

这里写图片描述

扫描二维码关注公众号,回复: 3789629 查看本文章

修改xpath后再来尝试一下,结果如下:

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

url = 'https://book.douban.com/top250'
data = requests.get(url).text
f = etree.HTML(data)
books = f.xpath('//*[@id="content"]/div/div[1]/div/table[1]/tr/td[2]/div[1]/a/@title')
print(books)

输出:

书名: ['追风筝的人']

完美,终于把数据搞下来了,接着往下走~~

切记:浏览器复制xpath 不是完全可靠的,看到tbady标签要特别注意,可以去网页源码看看是否这一层级。

2、爬取多本书名的xpath信息

对比不同的书名的xpath信息,分别复制《追风筝的人》,《小王子》,《围城》,《解忧杂货铺》“书名”的xpath信息进行对比:

这里写图片描述

比较可以发现书名的xpath信息仅仅table后的序号不一样,并且跟书的序号一致,于是去掉序号,我们就可以得到所有书名通用的xpath信息:

这里写图片描述

OK,我们试着把这一页所有书名都爬取下来:

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

url = 'https://book.douban.com/top250'
data = requests.get(url).text
f = etree.HTML(data)
books = f.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[1]/a/@title')
for book in books:
    print ("书名:",book)

结果如下:

书名: 追风筝的人
书名: 小王子
书名: 围城
书名: 解忧杂货店
书名: 活着
书名: 白夜行
书名: 挪威的森林
书名: 嫌疑人X的献身
书名: 三体
书名: 不能承受的生命之轻
书名: 红楼梦
书名: 梦里花落知多少
书名: 达·芬奇密码
书名: 看见
书名: 百年孤独
书名: 1988:我想和这个世界谈谈
书名: 何以笙箫默
书名: 平凡的世界(全三部)
书名: 简爱
书名: 哈利·波特与魔法石
书名: 白夜行
书名: 三体Ⅱ
书名: 飘
书名: 送你一颗子弹
书名: 三体Ⅲ

这样,就把所有书名都爬取下来了,我们继续啊,爬取评分,评价

3、爬取多本书的评分xpath信息

分别复制《追风筝的人》,《小王子》,《围城》,《解忧杂货铺》“评分”的xpath信息进行对比:

这里写图片描述

发现,还是table序号不一样,所以,你已经可以快速写出页面全部评分的xpath信息了:

//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[2]/span[2]

#记得删掉恶心的tbady

虽然你狠自信,但还是要看一下 能否把评分爬取下来:

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

url = 'https://book.douban.com/top250'
data = requests.get(url).text
f = etree.HTML(data)
scores = f.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[2]/span[2]/text()')
for score in scores:
    print ('评分:',score)

结束输出:

评分: 8.9
评分: 9.0
评分: 8.9
评分: 8.6
评分: 9.1
评分: 9.1
评分: 8.0
评分: 8.9
评分: 8.8
评分: 8.5
评分: 9.6
评分: 7.1
评分: 8.2
评分: 8.8
评分: 9.2
评分: 7.9
评分: 7.8
评分: 9.0
评分: 8.5
评分: 9.0
评分: 9.2
评分: 9.3
评分: 9.3
评分: 8.6
评分: 9.2

OK,接下来我们把两种元素合在一起进行爬取:

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

url = 'https://book.douban.com/top250'
data = requests.get(url).text
f = etree.HTML(data)
books = f.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[1]/a/@title')
scores = f.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[2]/span[2]/text()')
for i in range(10):
    print ("{}--->{}".format(books[i],scores[i]))

结果输出:

追风筝的人--->8.9
小王子--->9.0
围城--->8.9
解忧杂货店--->8.6
活着--->9.1
白夜行--->9.1
挪威的森林--->8.0
嫌疑人X的献身--->8.9
三体--->8.8
不能承受的生命之轻--->8.5

看起来是不是很简单,是不是感觉登上了人生巅峰了~~
但其实这里面的坑你是想不到的。

4、异常处理

仔细看下面代码:

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

url = 'https://book.douban.com/top250'
data = requests.get(url).text
f = etree.HTML(data)
books = f.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[1]/a/@title')
scores = f.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[2]/span[2]/text()')
for i in range(10):
    print ("{}--->{}".format(books[i],scores[i]))

这里我们默认书名和评分爬到的都是完全、正确的信息,这种默认情况一般没有问题,但其实是有缺陷的,如果我们某一项少爬或者多爬了信息,或者有些书根本就没有评分,那么两种数据的量就不一样了,从而匹配错误,比如下面的例子~~

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

url = 'https://book.douban.com/top250'
data = requests.get(url).text
f = etree.HTML(data)
books = f.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[1]/a/text()')#注意,我这里是获取文本,不是title了
scores = f.xpath('//*[@id="content"]/div/div[1]/div/table/tr/td[2]/div[2]/span[2]/text()')
for i in range(10):
    print ("{}--->{}".format(books[i],scores[i]))

书名xpath后的title改为text()
获取的文本数量与评分数量不一致,出现匹配错误。
结果:


                追风筝的人


              --->8.9

                小王子


              --->9.0

                围城


              --->8.9

                解忧杂货店


              --->8.6

                活着


              --->9.1

                白夜行


              --->9.1

                挪威的森林


              --->8.0

                嫌疑人X的献身


              --->8.9

                三体


                  --->8.8

              --->8.5

那你说怎么办??怎么办???

5、爬取整本书的信息

如果我们以每本书为单位,分别获取对应的信息

这里写图片描述

书名的标签肯定在这本书的框架内,于是我们从书名的标签上找,发现覆盖整本书的标签(左边网页会有代码包含内容的信息)。把xpath信息复制下来:

//*[@id="content"]/div/div[1]/div/table[1]

我们将整本书和书名、评分的xpath信息进行对比:

这里写图片描述

不难发现,书名和评分 xpath 的前半部分和整本书的xpath 一致
那我们可以通过这样写 xpath 的方式来定位信息:

book = f.xpath('//*[@id="content"]/div/div[1]/div/table[1]')
title = title = div.xpath('./tr/td[2]/div[1]/a/@title')
score = div.xpath('./tr/td[2]/div[2]/span[2]/text()')

ok ,在实际代码中实现一下:

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

url = 'https://book.douban.com/top250'
data = requests.get(url).text
f = etree.HTML(data)
books = f.xpath('//*[@id="content"]/div/div[1]/div/table[1]')
for div in books:
    title = div.xpath('./tr/td[2]/div[1]/a/@title')
    score = div.xpath('./tr/td[2]/div[2]/span[2]/text()')
    print("{}--->{}".format(title,score))

结果输出:

['追风筝的人']--->['8.9']

刚刚我们爬了一本书的信息,那想要爬取真个页面或者多个页面的信息呢?很简单啊,把xpath中定位的序号续貂就ok了。

这里写图片描述

来看一下新代码:

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

url2 = 'https://book.douban.com/top250'
data2 = requests.get(url2).text
f = etree.HTML(data2)
books = f.xpath('//*[@id="content"]/div/div[1]/div/table')
for div in books:
    title = div.xpath('./tr/td[2]/div[1]/a/@title')[0]
    score = div.xpath('./tr/td[2]/div[2]/span[2]/text()')[0]
    print("{}--->{}".format(title,score))

结果输出:

追风筝的人--->8.9
小王子--->9.0
围城--->8.9
解忧杂货店--->8.6
活着--->9.1
白夜行--->9.1
挪威的森林--->8.0
嫌疑人X的献身--->8.9
三体--->8.8
不能承受的生命之轻--->8.5
红楼梦--->9.6
梦里花落知多少--->7.1
达·芬奇密码--->8.2
看见--->8.8
百年孤独--->9.2
1988:我想和这个世界谈谈--->7.9
何以笙箫默--->7.8
平凡的世界(全三部)--->9.0
简爱--->8.5
哈利·波特与魔法石--->9.0
白夜行--->9.2
三体Ⅱ--->9.3
--->9.3
送你一颗子弹--->8.6
三体Ⅲ--->9.2

是吧,终于看到完整代码了,不过,等等~~~

title = div.xpath('./tr/td[2]/div[1]/a/@title')[0]
score = div.xpath('./tr/td[2]/div[2]/span[2]/text()')[0]

这里为什么后面多了个 [0] 呢?
我们之前爬出来的数据是以列表的形式输出,像这样

['追风筝的人']--->['8.9']

外面带个框不好看,列表只有一个值,对其去一个值就可以了,根据我们列表的访问方式,我门取第一个就行,不会的可以去看下列表的操作啊。

接下来,就照猫画虎,按照同样的方式多爬几条数据下来,比如评价人数、一句话总结、对应的链接等。

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

url = 'https://book.douban.com/top250'
data = requests.get(url).text
f = etree.HTML(data)
books = f.xpath('//*[@id="content"]/div/div[1]/div/table')
for div in books:
    title = div.xpath('./tr/td[2]/div[1]/a/@title')[0]
    score = div.xpath('./tr/td[2]/div[2]/span[2]/text()')[0]
    comment = div.xpath('./tr/td[2]/p[2]/span/text()')[0]
    num = div.xpath('./tr/td[2]/div[2]/span[3]/text()')[0].strip('(').strip().strip(')')
    href = div.xpath('./tr/td[2]/div[1]/a/@href')[0]


    if len(comment)>0:
        print('{}-->{}-->{}-->{}-->{}'.format(title,score,comment,num,href))
    else:
        print('{}-->{}-->{}-->{}'.format(title,score,num,href))
追风筝的人-->8.9-->为你,千千万万遍-->300174人评价
                -->https://book.douban.com/subject/1770782/


小王子-->9.0-->献给长成了大人的孩子们-->234507人评价
                -->https://book.douban.com/subject/1084336/


围城-->8.9-->对于“人艰不拆”四个字最彻底的违抗-->194599人评价
                -->https://book.douban.com/subject/1008145/


解忧杂货店-->8.6-->一碗精心熬制的东野牌鸡汤,拒绝很难-->262603人评价
                -->https://book.douban.com/subject/25862578/

有一点需要注意:

num = div.xpath('./tr/td[2]/div[2]/span[3]/text()')[0].strip('(').strip().strip(')')

这行代码用了几个strip() 方法,() 里面表示要删除的内容,strip(‘(‘) 表示删除括号,strip() 表示删除空白符。
如果不用strip() 方法,有很多无用的信息会被爬取,内容混乱,如下:

追风筝的人-->8.9-->为你,千千万万遍-->(
                    300174人评价
                )-->https://book.douban.com/subject/1770782/


小王子-->9.0-->献给长成了大人的孩子们-->(
                    234507人评价
                )-->https://book.douban.com/subject/1084336/


围城-->8.9-->对于“人艰不拆”四个字最彻底的违抗-->(
                    194599人评价
                )-->https://book.douban.com/subject/1008145/

爬取了括号等不必要的信息。所以我们把他们删除。

6、爬取多个页面的图书信息

OK,已经搞定一个页面了,下面我们爬取所有页面,共25页的信息。

先来看一下翻页后,url 是如何变化的。
第一页:https://book.douban.com/top250?start=0
第二页:https://book.douban.com/top250?start=25
第三页:https://book.douban.com/top250?start=50
第四页:https://book.douban.com/top250?start=75
…… ……..

url 变化很规律,只是start=() 的数字不一样而已
而且是已每页25为单位递增。什么?25?这不正是每页书籍的数量吗?
所以只需要加个循环即可。

写一个简单循环来遍历所有 url :

for i in range(10):
    url = 'https://book.douban.com/top250?start{}'.format(i*25)
    #总共10页,用 i*25 保证已25为单位递增

OK,这样就可以运行完整代码了。真是不容易,但是想通了也就很容易了。赶快去试试吧~~~~

#-*- coding:utf-8 -*-
import requests
from lxml import etree
import time

for i in range(10):
    url = 'https://book.douban.com/top250?start={}'.format(i*25)
    data = requests.get(url).text
    f = etree.HTML(data)
    books = f.xpath('//*[@id="content"]/div/div[1]/div/table')
    for div in books:
        title = div.xpath('./tr/td[2]/div[1]/a/@title')[0]
        score = div.xpath('./tr/td[2]/div[2]/span[2]/text()')[0]
        comment = div.xpath('./tr/td[2]/p[2]/span/text()')
        num = div.xpath('./tr/td[2]/div[2]/span[3]/text()')[0].strip('(').strip().strip(')')
        href = div.xpath('./tr/td[2]/div[1]/a/@href')[0]
        time.sleep(1) #加个睡眠,防止IP被封

        if len(comment)>0:
            print('{}-->{}-->{}-->{}-->{}'.format(title,score,comment[0],num,href))
        else:
            print('{}-->{}-->{}-->{}'.format(title,score,num,href))
        print('\n')

注意:这里加个sleep语句,以免爬太快,IP被封,虽然豆瓣反爬虫力度不大,但还是要养成这个习惯,去爬任何网站都适用。
另外:python的 range() 语法不会用的童鞋,自己要看看啊,毕竟基础很重要的。

下一篇将讲解 python爬虫实践——零基础快速入门(四)爬取小猪租房信息敬请期待哦。

猜你喜欢

转载自blog.csdn.net/MTbaby/article/details/79174036
今日推荐