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

admin   ·   发表于 8个月前   ·   编程代码

上一篇文章讲的是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 标签,我们需要手动把这些标签删掉。


修改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() 语法不会用的童鞋,自己要看看啊,毕竟基础很重要的。


0 Reply   |  Until 8个月前 | 491 View
LoginCan Publish Content