`中提取书名、价格、评价等级,在scrapy shell中尝试提取这些信息:
```
>>> sel = response.css('div.product_main')
>>> sel.xpath('./h1/text()').extract_first()
'A Light in the Attic'
>>> sel.css('p.price_color::text').extract_first()
'£51.77'
>>> sel.css('p.star-rating::attr(class)').re_first('star-rating ([A-Za-z]+)')
'Three'
```
另外,可在页面下端位置的`
`中提取产品编码、库存量、评价数量,在scrapy shell中尝试提取这些信息:
>>> sel = response.css('table.table.table-striped')
>>> sel.xpath('(.//tr)[1]/td/text()').extract_first()
'a897fe39b1053632'
>>> sel.xpath('(.//tr)[last()-1]/td/text()').re_first('\((\d+) available\)')
'22'
>>> sel.xpath('(.//tr)[last()]/td/text()').extract_first()
'0'
分析完书籍页面后,接着分析如何在书籍列表页面中提取每一个书籍页面的链接。在scrapy shell中,先调用fetch函数下载第一个书籍列表页面( http://books.toscrape.com/ ),下载完成后再调用view函数在浏览器中查看页面,如图8-5所示。
```
>>> fetch('http://books.toscrape.com/')
[scrapy] DEBUG: Crawled (200) (referer: None)
>>> view(response)
```
![bs64](https://img.handsomemark.com/2019/10/19/742c78ea-f228-11e9-9e66-acde48001122.png)
图8-5
每个书籍页面的链接可以在每个``中找到,在scrapy shell中使用LinkExtractor提取这些链接:
>>> from scrapy.linkextractors import LinkExtractor
>>> le = LinkExtractor(restrict_css='article.product_pod')
>>> le.extract_links(response)
[Link(url='http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html', text='',
fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/tipping-the-velvet_999/index.html', text='', fragment='',
nofollow=False),
Link(url='http://books.toscrape.com/catalogue/soumission_998/index.html', text='', fragment='',
nofollow=False),
Link(url='http://books.toscrape.com/catalogue/sharp-objects_997/index.html', text='', fragment='',
nofollow=False),
Link(url='http://books.toscrape.com/catalogue/sapiens-a-brief-history-of-humankind_996/index.html',
text='', fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/the-requiem-red_995/index.html', text='', fragment='',
nofollow=False),
Link(url='http://books.toscrape.com/catalogue/the-dirty-little-secrets-of-getting-your-dream-job_994/i
ndex.html', text='', fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/the-coming-woman-a-novel-based-on-the-life-of-the-in
famous-feminist-victoria-woodhull_993/index.html', text='', fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/the-boys-in-the-boat-nine-americans-and-their-epic-que
st-for-gold-at-the-1936-berlin-olympics_992/index.html', text='', fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/the-black-maria_991/index.html', text='', fragment='',
nofollow=False),
Link(url='http://books.toscrape.com/catalogue/starving-hearts-triangular-trade-trilogy-1_990/index.ht
ml', text='', fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/shakespeares-sonnets_989/index.html', text='',
fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/set-me-free_988/index.html', text='', fragment='',
nofollow=False),
Link(url='http://books.toscrape.com/catalogue/scott-pilgrims-precious-little-life-scott-pilgrim-1_987/i
ndex.html', text='', fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/rip-it-up-and-start-again_986/index.html', text='',
fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/our-band-could-be-your-life-scenes-from-the-american
-indie-underground-1981-1991_985/index.html', text='', fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/olio_984/index.html', text='', fragment='',
nofollow=False),
Link(url='http://books.toscrape.com/catalogue/mesaerion-the-best-science-fiction-stories-1800-1849_
983/index.html', text='', fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/libertarianism-for-beginners_982/index.html', text='',
fragment='', nofollow=False),
Link(url='http://books.toscrape.com/catalogue/its-only-the-himalayas_981/index.html', text='',
fragment='', nofollow=False)]
到此,页面分析的工作已经完成了。
# 3、编码实现
首先创建一个Scrapy项目,取名为toscrape_book。
```
$ scrapy startproject toscrape_book
```
通常,我们不需要手工创建Spider文件以及Spider类,可以使用scrapy genspider 命令生成(根据模板)它们,该命令的两个参数分别是Spider的名字和所要爬取的域(网站):
```
$ cd toscrape_book
$ scrapy genspider books books.toscrape.com
```
运行后,scrapy genspider命令创建了文件toscrape_book/spiders/books.py,并在其中创建了一个BooksSpider类,代码如下:
```
# -*- coding: utf-8 -*-
import scrapy
class BooksSpider(scrapy.Spider):
name = "books"
allowed_domains = ["books.toscrape.com"]
start_urls = ['http://books.toscrape.com/']
def parse(self, response):
pass
```
实现Spider之前,先定义封装书籍信息的Item类,在toscrape_book/items.py中添加如下代码:
```
class BookItem(scrapy.Item):
name = scrapy.Field() # 书名
price = scrapy.Field() # 价格
review_rating = scrapy.Field() # 评价等级,1~5 星
review_num = scrapy.Field() # 评价数量
upc = scrapy.Field() # 产品编码
stock = scrapy.Field() # 库存量
```
接下来,按以下5步完成BooksSpider。
步骤 01 继承Spider创建BooksSpider类(已完成)。
步骤 02 为Spider取名(已完成)。
步骤 03 指定起始爬取点(已完成)。
步骤 04 实现书籍列表页面的解析函数。
步骤 05 实现书籍页面的解析函数。
其中前3步已经由scrapy genspider命令帮我们完成,不需做任何修改。
第4步和第5步的工作是实现两个页面解析函数,因为起始爬取点是一个书籍列表页面,我们就将parse方法作为书籍列表页面的解析函数,另外,还需要添加一个parse_book方法作为书籍页面的解析函数,代码如下:
```
class BooksSpider(scrapy.Spider):
name = "books"
allowed_domains = ["books.toscrape.com"]
start_urls = ['http://books.toscrape.com/']
# 书籍列表页面的解析函数
def parse(self, response):
pass
# 书籍页面的解析函数
def parse_book(self, reponse):
pass
```
先来完成第4步,实现书籍列表页面的解析函数(parse方法),需要完成以下两个任务:
(1)提取页面中每一个书籍页面的链接,用它们构造Request对象并提交。
(2)提取页面中下一个书籍列表页面的链接,用其构造Request对象并提交。
提取链接的具体细节在页面分析时已经讨论过,实现代码如下:
```
class BooksSpider(scrapy.Spider):
name = "books"
allowed_domains = ["books.toscrape.com"]
start_urls = ['http://books.toscrape.com/']
# 书籍列表页面的解析函数
def parse(self, response):
# 提取书籍列表页面中每本书的链接
le = LinkExtractor(restrict_css='article.product_pod h3')
for link in le.extract_links(response):
yield scrapy.Request(link.url, callback=self.parse_book)
# 提取"下一页"的链接
le = LinkExtractor(restrict_css='ul.pager li.next')
links = le.extract_links(response)
if links:
next_url = links[0].url
yield scrapy.Request(next_url, callback=self.parse)
# 书籍页面的解析函数
def parse_book(self, response):
pass
```
最后完成第5步,实现书籍页面的解析函数(parse_book方法),只需提取书籍信息存入BookItem对象即可。同样,提取书籍信息的细节也在页面分析时讨论过,最终完成代码如下:
```
import scrapy
from scrapy.linkextractors import LinkExtractor
from ..items import BookItem
class BooksSpider(scrapy.Spider):
name = "books"
allowed_domains = ["books.toscrape.com"]
start_urls = ['http://books.toscrape.com/']
def parse(self, response):
le = LinkExtractor(restrict_css='article.product_pod h3')
for link in le.extract_links(response):
yield scrapy.Request(link.url, callback=self.parse_book)
le = LinkExtractor(restrict_css='ul.pager li.next')
links = le.extract_links(response)
if links:
next_url = links[0].url
yield scrapy.Request(next_url, callback=self.parse)
def parse_book(self, response):
book = BookItem()
sel = response.css('div.product_main')
book['name'] = sel.xpath('./h1/text()').extract_first()
book['price'] = sel.css('p.price_color::text').extract_first()
book['review_rating'] = sel.css('p.star-rating::attr(class)')\
.re_first('star-rating ([A-Za-z]+)')
sel = response.css('table.table.table-striped')
book['upc'] = sel.xpath('(.//tr)[1]/td/text()').extract_first()
book['stock'] = sel.xpath('(.//tr)[last()-1]/td/text()')\
.re_first('\((\d+) available\)')
book['review_num'] = sel.xpath('(.//tr)[last()]/td/text()').extract_first()
yield book
```
完成代码后,运行爬虫并观察结果:
```
$ scrapy crawl books -o books.csv --nolog
$ cat -n books.csv
1 name,stock,price,review_num,review_rating,upc
2 Scott Pilgrim's Precious Little Life,19,£52.29,0,Five,3b1c02bac2a429e6
3 It's Only the Himalayas,19,£45.17,0,Two,a22124811bfa8350
4 Olio,19,£23.88,0,One,feb7cc7701ecf901
5 Rip it Up and Start Again,19,£35.02,0,Five,a34ba96d4081e6a4
... 省略中间输出 ...
999 Bright Lines,1,£39.07,0,Five,230ac636ea0ea415
1000 Jurassic Park (Jurassic Park #1),3,£44.97,0,One,a0dd11f6abc421ec
1001 Into the Wild,3,£56.70,0,Five,a7c3f1010d64799a
```
从以上结果中看出,我们成功地爬取了网站中1000本书的详细信息,但也有让人不满意的地方,比如csv文件中各列的次序是随机的,看起来比较混乱,可在配置文件settings.py中使用FEED_EXPORT_FIELDS指定各列的次序:
```
FEED_EXPORT_FIELDS = ['upc', 'name', 'price', 'stock', 'review_rating', 'review_num']
```
另外,结果中评价等级字段的值是One、Two、Three……这样的单词,而不是阿拉伯数字,阅读起来不是很直观。下面实现一个Item Pipeline,将评价等级字段由单词映射到数字(或许这样简单的需求使用Item Pipeline有点大材小用,主要目的是带领大家复习之前所学的知识)。在pipelines.py中实现BookPipeline,代码如下:
```
class BookPipeline(object):
review_rating_map = {
'One': 1,
'Two': 2,
'Three': 3,
'Four': 4,
'Five': 5,
}
def process_item(self, item, spider):
rating = item.get('review_rating')
if rating:
item['review_rating'] = self.review_rating_map[rating]
return item
```
在配置文件settings.py中启用BookPipeline:
```
ITEM_PIPELINES = {
'toscrape_book.pipelines.BookPipeline': 300,
}
```
重新运行爬虫,并观察结果:
```
$ scrapy crawl books -o books.csv
...
$ cat -n books.csv
1 upc,name,price,stock,review_rating,review_num
2 a897fe39b1053632,A Light in the Attic,£51.77,22,3,0
3 3b1c02bac2a429e6,Scott Pilgrim's Precious Little Life,£52.29,19,5,0
4 a22124811bfa8350,It's Only the Himalayas,£45.17,19,2,0
5 feb7cc7701ecf901,Olio,£23.88,19,1,0
... 省略中间输出 ...
999 91eb9605998a7c03,"The Sandman, Vol. 3: Dream Country",£55.55,3,5,0
1000 f06039c29b5891fa,The Silkworm (Cormoran Strike #2),£23.05,3,5,0
1001 476c7972e9b41891,The Last Painting of Sara de Vos,£55.55,3,2,0
```
此时,各字段已按指定次序排列,并且评价等级字段的值是我们所期望的阿拉伯数字。
到此为止,整个项目完成了。
# 4、本章小结
本章是基础篇的最后一章,通过一个Scrapy爬虫项目复习了之前章节所学的知识,现在,大家已经能够编写一个一般任务的Scrapy爬虫了,可以通过更多的实战项目进行练习,在后面的章节中,我们将会学习一些高级话题。