0%

Scrapy爬取豆瓣小Demo

具体流程分析:

  1. 进入热门电影页面

  2. 抓取所有电影链接

    1. 抓取每个电影的标题、评分、首页评论
  3. 进入下一页继续递归爬取

Scrapy基本架构:

  • 引擎(Scrapy Engine):处理整个系统的数据流,触发事务。
  • 调度器(Scheduler):接受引擎发过来的请求,压入调度队列,并在引擎再次请求的时候返回。
  • 下载器(Downloader):下载网页内容,并将网页内容返回给蜘蛛。
  • 蜘蛛(Spider):定制特定域名或网页的解析规则。
  • 项目管道(ItemPipeline):处理由蜘蛛从网页中提取的结构性数据,主要任务是清洗、验证和存储数据。
  • 中间件(Middlewares):包括下载器中间件(引擎与下载器之间)、蜘蛛中间件(引擎与蜘蛛之间)、调度中间件(引擎与调度器之间)等。

实战:

创建项目:

1
2
3
scrapy startproject doubanmoviePract
cd doubanmoviePract
scrapy genspider crawler movie.douban.com
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
文件结构:
F:.
│ doubanmp_crawler - 副本.log
│ doubanmp_crawler.log
│ re.csv
│ scrapy.cfg

└─doubanmoviePract
│ items.py
│ middlewares.py
│ pipelines.py
│ settings.py
│ __init__.py

└─spiders
crawler.py
__init__.py

自定义结构性数据:

首先编辑根目录下的items文件,在项目对象下设置自己想要爬取的信息,这里设置了标题、评分、网址链接、首页的评论信息

1
2
3
4
5
6
7
8
\doubanmoviePract\doubanmoviePract\items.py

import scrapy
class DoubanmoviepractItem(scrapy.Item):
title = scrapy.Field()
rate = scrapy.Field()
url = scrapy.Field()
first_p_comment = scrapy.Field()

编写解析网页的spider:

解析网页的spider是位于spider目录下的crawler文件,这里我主要增加了

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
import scrapy
from urllib.parse import urlencode
import json
from ..items import DoubanmoviepractItem

class CrawlerSpider(scrapy.Spider):
name = 'crawler'
allowed_domains = ['movie.douban.com']
start_urls = ['https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=100&page_start=0']

def parse(self, response):
jsonresponse = json.loads(response.text)
for j in jsonresponse['subjects']:
request= scrapy.Request(response.urljoin(j["url"]), callback=self.comment_parse)
request.meta['j'] = j
yield request

def comment_parse(self,response):
item = DoubanmoviepractItem()
j = response.meta['j']
item["title"] = j["title"]
item["rate"] = j["rate"]
item["url"] = j["url"]
item["first_p_comment"] = response.css('.short').getall()
yield item

首先从豆瓣的/j接口网页中获得热门电影的信息,然后将标题、评分、链接放到字典的不同值里,并且将链接yield给comment_parse进行首页评论的爬取。结构相对比较简单。

这里有几个地方花费了大量的时间理解

如何让不同的函数对同一对象进行操作

如何在callback回调中传输参数

第一个问题,如何让不同的函数对同一个对象进行操作:

因为面向对象的知识实在是过于久远了,很多东西都还给课本了,我一开始直接按逻辑思路在两个函数中对item的不同属性进行操作,但是操作的前提是有item对象,所以我就在每个方法中都生成了一个item对象,结果是,程序运行后只保留了后一个对象,于是我打算只建一个对象,然后两个函数对同一个对象进行操作,结果是我没法确定应该在哪新建这个对象,因为在哪新建她都给我报错,于是我决定看看怎么把”对对象的操作“放进一个函数里,于是引出了第二个问题,如何在回调中传递参数:

参考链接里的方法是,将parse中的Item对象直接加到request里,然后通过response调出这个对象之后交给other_parse,因为我当时已经在下面写好了item的操作了,所以只把网页信息加到了request里。

自定义数据管道

通常这一步进行的是如何清洗并存储你的数据,这里由于是小规模爬取,我就没有进行数据的存储。

自定义中间件

中间件即项目目录下的middlewares文件,可以实现的功能有很多,这两天大概了解了随机UA和动态ip的使用,由于动态ip比较费劲,而且已经23:54了,我就没接着折腾,随机UA会减缓一定程度的封锁,但是动态ip更吊一些。

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
class DoubanmoviepractDownloaderMiddleware:
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the downloader middleware does not modify the
# passed objects.
'''
使用随机的User-Agent进行爬虫
'''
def __init__(self):
# User-Agent 列表:https://github.com/fengzhizi715/user-agent-list/blob/master/Chrome.txt
self.user_agent_list = [
'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36',
'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36',
'Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36',
'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36',
'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2117.157 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'
]

def process_request(self, request, spider):
ua = random.choice(self.user_agent_list)
request.headers['User-Agent'] = ua
return None

设置settings

前面设置的pipeline和middlewares在自己定义好后需要从设置里注册并设置优先级:

1
2
3
4
5
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'doubanmoviePract.middlewares.DoubanmoviepractDownloaderMiddleware': 100,
}
ROBOTSTXT_OBEY = False

这里一个问题就是,在设置随机UA时,要把系统的UA给关掉。以及要把ROBOTSTXT_OBEY 设为False,不然的话,scrapy会遵守目标网站的规则,而通常,目标网站是不会让普通机器人爬取信息的。

启动spider

1
scrapy crawl crawler -o re.csv

因为没有设置数据存储的中间件,所以在启动程序的时候,直接 ”-o 目标文件“即可输出定义的值。

检查爬虫结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
{'first_p_comment': ['<span '
'class="short">最后十分钟眼泪不止,他明明没有表情,但我看懂了他的所有情绪,这就是爱!真他娘的又浪漫又悲壮</span>',
'<span class="short">2021,听起来多么适合作为世界末日的一个年份。</span>',
'<span '
'class="short">真的,狗子不管和谁玩球,最后都会把对方丢出去的球捡回叼给主人(主人在场的情况)。我们豆小葵十年如一日!末日公路片,一人一机器人一狗子,两个小时从绝望到慢慢被灌注希望和光明。Good '
'year,真的很喜欢狗子的这个名字,2019年可能是未来10年来最好的一年,疫情、经济、气候变化...都形势严 峻,芬奇所在的未来,臭氧层已经被严重破坏,人类为了生存时刻要泯灭良心挑战人性底线。而他把狗子托给杰夫照顾,这...</span>',
'<span '
'class="short">一人,一机器,一条狗,在世界末日里享受生活,简简单单,却让人心情舒畅,倍感惬意。这是一部几乎没有高潮的片子,但当狗狗把球丢在芬奇面前时,我却热泪盈眶。</span>',
'<span '
'class="short">想一想未来社会,有机器人收尸送终也不错。况且还是穿派克大衣、翘兰花指的机器人,又能帮你喂狗。《我是传奇》《火星救援》《荒岛余生》式的鲁滨逊孤独生存故事。只要日子里依然有书、有狗、有音乐和伙伴,就能不那么像末世。最有意味的一幕:杰夫发现了镜子,也第一次发现了“我自己”。</span>'],
'rate': '8.4',
'title': '芬奇',
'url': 'https://movie.douban.com/subject/26897885/'}

这里没有进行数据清洗,所以还有很多html标签。

源码

由于只是个小demo,代码也还有很多需要改善的地方,主要有两点,一个是数据清洗,一个是动态ip的设置,所以就不贴代码了。

参考链接:

Scrapy项目实战—捡贝壳的男孩

scrapy回调函数传递参数