开头再来波小程序摇一摇:php
上一节,咱们说到了爬取普通页面的每一条item怎么搞。相信你们以及对 CrawlSpider 里面的 Rule 有所了解。css
接下来,今天咱们就作一个完结。写一个通用的CrawlSpider。html
为啥更新的这么慢?由于正则表达式
以前教程拿来作试验的网站是“每天美剧”,http://www.ttmeiju.me/, 可是鬼知道,特么的写了两篇文章以后,因为不可抗拒力,每天美剧被下线了。卧了个槽,这岂不是以前写的全白搭了吗?萌新小白想学习爬虫,看了代码,可是例子跑步起来,这尼玛怎么搞?学习体验及其差的好伐啦。shell
那么今天的内容,其实仍是和之前是衔接的起来的,只不过咱们换了一个网站而已,并且这个网站更加的有趣,工整。地址我就不说了,代码里面都有,文章主要是着重讲解一下写一个灵活爬虫的关键点。数据库
因为以前的每天美剧挂了,因此咱们更新了一个更加有意思的网站来做为爬虫爬取的对象。具体的网址就不说了,感兴趣的同窗能够在代码里面看到。代码的获取方式:关注公号『皮爷撸码』,回复『代码』便可找到,记住对应的代码编号是『CS001』,代码编号是『CS001』,代码编号是『CS001』。编程
这个网站呢,大概就是张这个样子:json
咱们作的就是要爬取这个网站。小程序
为了方便学习交流,咱们打算先来拿某类别的做品集合做为 start_url 。好比(域名并不是正确):bash
http://jxxpop.com/idol
好比这个地址,里面就是写真集合。
其实,作这些东西,咱们只须要写出来两个 Rule 就能够,一个是 下一页的Rule
, 另外一个则是每个 item 的 Rule
。
怎么找这两个Rule的xpath?我在上一篇文章中就讲到了,能够经过 scrapy shell 来作。我这里就再也不重复,找到以后,结果以下:
rules = ( Rule(LinkExtractor(allow='.*\.html', restrict_xpaths='//div[@class="entry"]//li/a[1]'), callback='parse_item'), Rule(LinkExtractor(restrict_xpaths='//div[@class="wp-pagenavi"]/a[contains(.,"Next")]')), )复制代码
上面的两个Rule,第一个对应的是寻找每个Item的Rule,看到他有个parse_item的回调函数, 第二个则是下一页的Rule。
接下来就是针对每个Item怎么解析的问题了。
咱们首先在 items.py
文件里面声明咱们的item类:
class JxxpopItem(scrapy.Item): video_url = scrapy.Field() video_title = scrapy.Field() video_num = scrapy.Field() video_img_poster = scrapy.Field() video_img_screenshot = scrapy.Field() video_tags = scrapy.Field()复制代码
能够看到,总共有这么六个变量。这留个变量就是咱们要在parse_item()函数里面解析出来的。接下来看咱们在每个item页面是如何解析出来页面中这些变量的。
寻找每个变量的xpath路径,一样是用到 Scrapy shell 来寻找。废话很少说,咱们来看 Parse_item() 方法长什么样子:
def parse_item(self, response): item = JxxpopItem() page_url = response.url page_title = response.xpath('//div[@class="box-b"]/h1/text()').extract_first() re_pattern = '\[.*\]' page_num_temp_list = re.findall(re_pattern, page_title) if page_num_temp_list: page_num = page_num_temp_list[0][1:-1] page_img_poster = response.xpath('//div[@class="box-b"]/div[@class="entry"]/p[@class="poster"]/img/@src').extract() page_img_screenshot = response.xpath('//div[@class="box-b"]/div[@class="entry"]/p[@class="screenshot"]/img/@src').extract() page_tag_list = response.xpath('//div[@class="box-b"]/div[@class="entry"]/div[@class="post-meta"]/div/p/a/text()').extract() item['video_title'] = page_title item['video_url'] = page_url item['video_num'] = page_num item['video_img_poster'] = page_img_poster item['video_img_screenshot'] = page_img_screenshot item['video_tags'] = page_tag_list print(item)复制代码
这里看到,为了寻找这几个变量,这里运用到的知识点比较多,既有xpath读取属性,又有正则表达式匹配。最后经过print(item)
只是把记过打印出来,若是想要让item走后面的流程,须要经过 yield item
来将 JxxpopItem 抛出来。咱们运行一下结果看一下:
恩,结果都打印出来了,很完美。
想要经过一个JSON文件来控制咱们的爬虫,首先在工程里面建立一个configs
文件夹,里面放置咱们的配置文件,同时,咱们还得有一个 utils.py
文件,里面提供咱们读取配置文件的方法,
#utils.pydef get_config(name): path = dirname(realpath(__file__)) + '/configs/' + name + '.json' with open(path, 'r', encoding='utf-8') as f: return json.loads(f.read())复制代码
同时,咱们须要将咱们的启动程序文件也要修改一下:
#Run.pydef run_spider_by_json(): name = sys.argv[1] custom_settings = get_config(name) spider = custom_settings.get('spider', 'jxxpop') project_settings = get_project_settings() settings = dict(project_settings.copy()) settings.update(custom_settings.get('settings')) process = CrawlerProcess(settings) process.crawl(spider, **{'name': name}) process.start()复制代码
接着,才是咱们的重头戏,修改 spider 文件。这里,咱们须要作这么几件事情。首先改写 __init__()
方法,咱们要在这个地方,将json 文件中的配置信息读取到 spider 里面,接着就是重写 parse_item() 方法,里面须要加入 itemLoader。这样才能更爽的来定制爬虫。
def __init__(self, name, *args, **kwargs): config = get_config(name) self.config = config self.rules = rules.get(config.get('rules')) start_urls = config.get('start_urls') if start_urls: if start_urls.get(j'type') == 'static': self.start_urls = start_urls.get('value') elif start_urls.get('type') == 'datetime': self.start_urls = list(eval('urls.' + start_urls.get('name'))(start_urls.get('date'))) self.allowed_domains = config.get('allowed_domains') super(JxxpopSpider, self).__init__(*args, **kwargs) def parse_item(self, response): item = self.config.get('item') if item: cls = eval(item.get('class'))() loader = eval(item.get('loader'))(cls, response=response) # 动态获取属性配置 for key, value in item.get('attrs').items(): for extractor in value: if extractor.get('method') == 'xpath': loader.add_xpath(key, *extractor.get('args'), **{'re': extractor.get('re')}) if extractor.get('method') == 'css': loader.add_css(key, *extractor.get('args'), **{'re': extractor.get('re')}) if extractor.get('method') == 'value': loader.add_value(key, *extractor.get('args'), **{'re': extractor.get('re')}) if extractor.get('method') == 'attr': loader.add_value(key, getattr(response, *extractor.get('args'))) # yield loader.load_item() print(loader.load_item())复制代码
这里可能比较晦涩,可是结合以前的 parse_item() 版本,和咱们的配置文件:
"start_urls": { "type": "datetime", "name": "jxxpop_url", "value": ["http://jxxpop.com/category/censored"], "date":"20190529" }, "allowed_domains": [ "jxxpop.com" ], "rules": "jxxpop", "item": { "class": "JxxpopItem", "loader": "JxxpopLoader", "attrs": { "video_url": [ { "method": "attr", "args": [ "url" ] } ], "video_title": [ { "method": "xpath", "args": [ "//div[@class='box-b']/h1/text()" ] } ], "video_num": [ { "method": "xpath", "args": [ "//div[@class='box-b']/h1/text()" ], "re": "\\[.*\\]" } ], "video_img_poster": [ { "method": "xpath", "args": [ "//div[@class='box-b']/div[@class='entry']/p[@class='poster']/img/@src" ] } ], "video_img_screenshot": [ { "method": "xpath", "args": [ "//div[@class='box-b']/div[@class='entry']/p[@class='screenshot']/img/@src" ] } ], "video_tags": [ { "method": "xpath", "args": [ "//div[@class='box-b']/div[@class='entry']/div[@class='post-meta']/div/p/a/text()" ] } ] } }复制代码
你就能理解的差很少了。每个变量,都对应着他在程序里面的解析方法,从配置文件里面读取出来,而后交给 loader 处理。
#itemloader.pyclass JxxpopLoader(ItemLoader): video_title_out = TakeFirst() video_url_out = TakeFirst() video_num_out = Compose(get_video_num) video_img_poster_out = Join()复制代码
这里面主要是涉及到一些 in
和 out
的处理。这里的TakeFirst()
则是取第一个; Compose(fun)
则是能够经过括号里面的函数来对结果作处理;Join()
则是将列表整合成一个。
能够看到配置文件里面,咱们这里要爬取的是5月29日的发布结果,运行看一下结果:
完美。
为啥要这么搞,主要缘由就是为了方便。由于爬虫和反爬,一直就是道和魔之间的斗争,你今天写好了爬虫,各类 xpath 都写死了,并且爬虫也部署了,结果明天,网站的页面结构发生变化了,你这岂不是还得改代码,而后从新发版?
可是若是采用经过配置文件来配置爬虫的方法,那么你须要作的,只是修改配置文件就能够了。这样能够在最大程度上削减发版的次数,并且效率还很高,也不用再次全面测试。
目前这个爬虫,只是把数据爬取的部分完成了。后续的步骤还有:将结果存入数据库,爬虫部署到服务器上。这两个步骤,我以前都写过。因此这个系列的文章,主要就是为了来让你们了解一下,怎样可以写出来一个能够经过灵活配置就能工做的爬虫。
由于文章都是涉及到服务器的,因此福利就要写在最前面:
过大年了,你们是否是又有了压岁钱了啊??啊哈哈哈哈,压岁钱买糖吃还不如投资到本身身上。好比用来买课程,或者用来买服务器,来学习编程,写爬虫。来买服务器啊买服务器啊!只在本地跑,根本没用的!恰巧,铲屎官这里就有上千元的阿里云和腾讯云的优惠券给你使用(每一款优惠只要点击优惠连接,进入便可领取):阿里云部分:
【阿里云新人1888元云产品通用代金券】:
promotion.aliyun.com/ntms/yunpar…【阿里云爆款云主机,2折优惠券】:
promotion.aliyun.com/ntms/act/qw…【阿里云企业级服务器2折优惠券】:
promotion.aliyun.com/ntms/act/en…腾讯云:
【新客户无门槛领取总价值高达2775元代金券,每种代金券限量500张,先到先得】:
cloud.tencent.com/redirect.ph…
代码我已经在公众号里面分享了,想要获取源码的同窗,能够关注公号『皮爷撸码』,回复『代码』便可找到,记住对应的代码编号是『CS001』,代码编号是『CS001』,代码编号是『CS001』。