Scrapy爬取豆瓣小Demo
/ 10 min read
Table of Contents
具体流程分析:
进入热门电影页面
抓取所有电影链接
- 抓取每个电影的标题、评分、首页评论
进入下一页继续递归爬取
Scrapy基本架构:
- 引擎(Scrapy Engine):处理整个系统的数据流,触发事务。
- 调度器(Scheduler):接受引擎发过来的请求,压入调度队列,并在引擎再次请求的时候返回。
- 下载器(Downloader):下载网页内容,并将网页内容返回给蜘蛛。
- 蜘蛛(Spider):定制特定域名或网页的解析规则。
- 项目管道(ItemPipeline):处理由蜘蛛从网页中提取的结构性数据,主要任务是清洗、验证和存储数据。
- 中间件(Middlewares):包括下载器中间件(引擎与下载器之间)、蜘蛛中间件(引擎与蜘蛛之间)、调度中间件(引擎与调度器之间)等。
实战:
创建项目:
scrapy startproject doubanmoviePractcd doubanmoviePractscrapy genspider crawler movie.douban.com文件结构: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文件,在项目对象下设置自己想要爬取的信息,这里设置了标题、评分、网址链接、首页的评论信息
\doubanmoviePract\doubanmoviePract\items.py
import scrapyclass DoubanmoviepractItem(scrapy.Item): title = scrapy.Field() rate = scrapy.Field() url = scrapy.Field() first_p_comment = scrapy.Field()编写解析网页的spider:
解析网页的spider是位于spider目录下的crawler文件,这里我主要增加了
import scrapyfrom urllib.parse import urlencodeimport jsonfrom ..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更吊一些。
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在自己定义好后需要从设置里注册并设置优先级:
DOWNLOADER_MIDDLEWARES = { 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, 'doubanmoviePract.middlewares.DoubanmoviepractDownloaderMiddleware': 100,}ROBOTSTXT_OBEY = False这里一个问题就是,在设置随机UA时,要把系统的UA给关掉。以及要把ROBOTSTXT_OBEY 设为False,不然的话,scrapy会遵守目标网站的规则,而通常,目标网站是不会让普通机器人爬取信息的。
启动spider
scrapy crawl crawler -o re.csv因为没有设置数据存储的中间件,所以在启动程序的时候,直接 ”-o 目标文件“即可输出定义的值。
检查爬虫结果:
{'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的设置,所以就不贴代码了。