这是一项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 import scrapyclass DoubanspItem (scrapy.Item): 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 scrapyfrom scrapy.spiders import Spiderfrom scrapy.selector import Selectorfrom doubansp.items import DoubanspItem2class 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 , } } def parse (self, response ): item = DoubanspItem2() sel = Selector(response) images = sel.xpath('//*[@id="content"]/div/div[1]/ol/li' ) item['img_url' ] = [] item['img_names' ] = [] for image in images: 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 reimport scrapyfrom scrapy.spiders import Spiderfrom scrapy.selector import Selectorfrom doubansp.items import DoubanspItemclass 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 ): selector = Selector(response) 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() item = DoubanspItem() item['movie_name' ] = movie_name item['movie_star' ] = movie_star item['movie_quote' ] = movie_quote yield item next_page = response.selector.xpath('//span[@class="next"]/link/@href' ).extract() if next_page: next_page = next_page[0 ] 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 ItemAdapterfrom scrapy import itemfrom scrapy.pipelines.images import ImagesPipelinefrom scrapy.http import Requestclass 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)}) 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' 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 osos.system("scrapy crawl imgdown" ) os.system("scrapy crawl db -o douban.json -t json" )
小结 入门Scrapy的坑还是很多的,我从早到晚就为了解决指定管道的问题,一开始剑走偏锋像网上查到的使用IF语句在管道内判断是哪个爬虫调用,然后一直出BUG或者直接没用,最后才发现直接在爬虫里指定管道就可以了,小问题浪费大时间,典中典了。