用Scrapy框架爬取豆瓣


这是一项Python爬虫课的课程综合实践作业。

前情提要

这个Python老师就喜欢整些花的,上课时间也飘忽不定,然后让我们用从来没用过的Scrapy框架在两天内自学完成一个简单爬虫,真不错,所以浅浅的学了一下,发现Scrapy爬虫的效率真的很高,而且稳定,用Xpath来定位标签也很方便。

分析

目标网站是豆瓣电影TOP250,还是和之前差不多,250个电影为每页25个分为10页, 每个电影包含其电影海报、电影评分、电影评论等信息。不同的是之前通过正则表达式过滤来从源码中获取想要的信息,而且也用到了bs,因为re虽然匹配强,但是无法对信息顺序有一个记录,而这次使用Xpath来定位信息并抓取,相比手动撰写的re正则表达式,能够在F12中复制的Xpath可太舒服了。

代码

创建Scrapy项目

crapy startproject doubansp

创建一个爬虫

先到刚才创建的文件夹下面:

cd doubansp

再创建两个爬虫并指定名称和其域名(其实可以手动创建):

scrapy genspider pictureSpider movie.douban.com

scrapy genspider imgdown movie.douban.com

items.py

在这个文件中定义一些容器来存放获取到的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class DoubanspItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()

# 信息容器
movie_name = scrapy.Field()
movie_star = scrapy.Field()
movie_quote = scrapy.Field()

class DoubanspItem2(scrapy.Item):
# 图片容器
img_url = scrapy.Field()
img_names = scrapy.Field()

spiders/imgdown.py

在这个文件中编写下载图片功能的爬虫代码。

这里使用custom_settings参数来指定这个爬虫使用的管道,防止图片下载的管道在运行获取信息的爬虫时造成数据错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import scrapy
from scrapy.spiders import Spider
from scrapy.selector import Selector
from doubansp.items import DoubanspItem2


class imgdown(Spider):
# 爬虫名称
name = "imgdown"
# 开始链接
start_urls = ["https://movie.douban.com/top250"]
for i in range(1, 10):
start_urls.append("https://movie.douban.com/top250?start=%d&filter=" % (25 * i))

# 指定使用管道
custom_settings = {
'ITEM_PIPELINES': {
'doubansp.pipelines.MyImagesPipeline': 100,
}
}


# parse function
def parse(self, response):

item = DoubanspItem2()
sel = Selector(response)
# 使用xpath定位图片链接所在位置
images = sel.xpath('//*[@id="content"]/div/div[1]/ol/li')

# 定义图片链接与名称容器
item['img_url'] = []
item['img_names'] = []
# append the url and name of the image in item
for image in images:
# extract url and name of the image
# 获取图片链接
site = image.xpath('div/div[1]/a/img/@src').extract_first()
# 获取图片名称
img_name = image.xpath('div/div[1]/a/img/@alt').extract_first()
# 获取电影排名顺序
rank = image.xpath('div[@class="item"]/div[@class="pic"]/em/text()').extract_first()

item['img_url'].append(site)
item['img_names'].append(rank + ' ' + img_name)

yield item

spiders/PicturespiderSpider.py

在这个文件中编写获取电影数据的爬虫代码,名字有picture是因为一开始准备在这个文件里编写图片下载功能的,但是后面出了点问题又在另一个爬虫文件里写,然后懒得改这个名字了,有需求可以自己改一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import re

import scrapy
from scrapy.spiders import Spider
from scrapy.selector import Selector
from doubansp.items import DoubanspItem

class PicturespiderSpider(scrapy.Spider):
# 设置模块名称
name = 'db'
# 所爬取的域名
allowed_domains = ['movie.douban.com']
# 开始的网址
start_urls = ['https://movie.douban.com/top250']

# 指定使用管道
custom_settings = {
'ITEM_PIPELINES': {
'doubansp.pipelines.DoubanspPipeline': 100,
}
}

def parse(self, response):
# print(response.body)
selector = Selector(response)

# print(selector)
# 定义Xpth所在标签,info包含一般电影信息
movies = selector.xpath('//div[@class="info"]')
for movie in movies:
# 定位电影名称所在位置
movie_name = movie.xpath('div[@class="hd"]/a/span/text()').extract()
for i in range(0,len(movie_name)):
movie_name[i] = re.sub('\xa0/\xa0', '', movie_name[i])
# 定位电影电影评分所在位置
movie_star = movie.xpath('div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text()').extract()
# 定位电影评论所在位置
movie_quote = movie.xpath('div[@class="bd"]/p[@class="quote"]/span[@class="inq"]/text()').extract()

#print(movie_name)
#print(movie_star)
#print(movie_quote)

item = DoubanspItem()

# 将爬取到的信息放进item中的容器当中
item['movie_name'] = movie_name
item['movie_star'] = movie_star
item['movie_quote'] = movie_quote
yield item
# print(movie_name)
# print(movie_star)
# print(movie_quote)

# 对下一页链接进行获取定位
next_page = response.selector.xpath('//span[@class="next"]/link/@href').extract()
if next_page:
next_page = next_page[0]
# 将下一页的标签参数进行更改递增25
yield scrapy.Request('https://movie.douban.com/top250' + next_page, callback=self.parse)

pipelines.py

在这个文件中,对Scrapy管道进行编辑,定义一个MyImagesPipeline类用于保存图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from itemadapter import ItemAdapter
from scrapy import item
from scrapy.pipelines.images import ImagesPipeline
from scrapy.http import Request


class xsPipeline:
def process_item(self, item, spider):
return item


# 图片下载管道
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
if item['img_url'] is not None:
for url in item['img_url']:
yield Request(url, meta={'item': item, 'index': item['img_url'].index(url)})

# rename the image
# 保存图片
def file_path(self, request, response=None, info=None):
# 获取图片
item = request.meta['item']
index = request.meta['index']

# 获取图片名称
image_name = item['img_name'][index]

# 设置图片名称并保存
return 'img/%s.jpg' % (image_name)

setting.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BOT_NAME = 'doubansp'

SPIDER_MODULES = ['doubansp.spiders']
NEWSPIDER_MODULE = 'doubansp.spiders'

# 指定编码格式
FEED_EXPORT_ENCODING = 'utf-8'

# 设置UA
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.41'

# 设置管道顺序(因为在爬虫文件中指定了,其实没必要)
ITEM_PIPELINES = {'doubansp.pipelines.DoubanspPipeline': 100,
'doubansp.pipelines.MyImagesPipeline':200 }

IMAGES_URLS_FIELD = 'url'
IMAGES_STORE = r'.'

main.py

令人折磨的其实是这里,因为我是分成两个爬虫写的,之前没有为图片爬虫指定管道,导致数据交叉产生错误,然后又使用scrapy自带的cmdline函数来执行启动爬虫的命令,它会导致执行完一条命令后,直接让程序关闭,然后就G了,所以想要多条爬虫按顺序运行,必须使用os.system函数来执行爬虫启动命令。

1
2
3
4
5
import os

os.system("scrapy crawl imgdown")
# 将爬取到的信息保存成josn文件
os.system("scrapy crawl db -o douban.json -t json")

小结

入门Scrapy的坑还是很多的,我从早到晚就为了解决指定管道的问题,一开始剑走偏锋像网上查到的使用IF语句在管道内判断是哪个爬虫调用,然后一直出BUG或者直接没用,最后才发现直接在爬虫里指定管道就可以了,小问题浪费大时间,典中典了。


评论