(代码文件链接见第六点)
1.实验目的
本次实验是为了掌握scrapy爬取层级网站信息的技巧。
2.实验要求
使用scrapy爬取Curlie网站的News目录下(https://curlie.org/News/)所有的子项目的网站标题、Url和网站描述信息,并且将News目录下面的子项目生成具有Json格式的目录结构的目录。
3.实验工具
centos2.7、Python3.6.5、scrapy1.5.0
4.实验方案设计
分析目标网页规则,制定爬虫策略--->编写Python代码--->进行代码调试---->处理爬虫数据,生成最后爬虫结果
5.实验过程
-
分析目标网页规则,制定爬虫策略
1.NEWS下面的Subcategories内容。如图
可以看出我们需要爬取的信息都在//section[@class="children"]//div[@class="cat-item"]/div标签中。
针对每一个Subcategories我们需要爬取的信息有
- 网站名称 name
- 网站url url
2.根据实验要求可以看出我们主要爬取的是NEWS下面的Subcategories 下的Sites的内容,由图可以看出,我们需要爬取的列表信息都在//section[@class='children']/div这个标签下。
针对每一个Sites我们需要爬取的信息有
这三个对象在网站中的位置为
因而我们小组的大致爬虫思路为:因为需要遍历News下的所有目录,所以我们小组选择使用适用于爬取批量网页的类CrawlSpider,用rules来提取页面的链接。linkextractor(allow=(正则表达式),restrict_xpaths=())提取页面内的超链接,allow限制只允许提取符合正则表达式的url,restrict_xpaths用xpath定位提取的url在页面的位置,和allow共同作用过滤链接。Rule从起始页用提取符合规则的链接,通过这个链接再生成Request,如此循环,直至没有符合要求的链接。并用callback解析返回的结果。
下面是我们的python代码思路解析
-
编写Python代码
items.py代码# -*- coding: utf-8 -*- # Define here the models for your scraped items # # See documentation in: # https://doc.scrapy.org/en/latest/topics/items.html import scrapy class CurlieItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() category = scrapy.Field() # 当前所属目录 subcategory = scrapy.Field() # 子目录 name = scrapy.Field() #子目录项目名字 url = scrapy.Field() #子目录项目url sites = scrapy.Field() #当前网页下的网站 title = scrapy.Field() # 网站标题 site_url = scrapy.Field() #网站url description = scrapy.Field() #网站概述
spider爬虫代码(curlie.py)
import scrapy from urllib import parse from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor from curlie.items import CurlieItem class CurlieSpider(CrawlSpider): name = "curlie" allowed_domains = ["curlie.org"] start_urls = ["https://curlie.org/News/" rules =( Rule(LinkExtractor(allow=(r'https://curlie.org/News/[\s\S]*'),restrict_xpaths = ('//section[@class="children"]//div[@class="cat-item"]')), callback="parse_item" ,follow=True),#只爬News路径下的 #Rule(LinkExtractor(restrict_xpaths=('//section[@class="children"]//div[@class="cat-item"]')),callback="parse_item", follow=True), #News子分类下的都爬) def parse_item(self,response): item = CurlieItem() #当前所属目录分类 item['category'] = response.url.lstrip('https://curlie.org') #当前目录子目录 item['subcategory'] = [] for sub in response.xpath('//section[@class="children"]//div[@class="cat-item"]'): subcategory = {} subcategory['name'] = sub.xpath('.//a/div/text()').extract()[1].strip() subcategory['url'] = parse.urljoin(response.url, sub.xpath('.//a/@href').extract_first()) item['subcategory'].append(subcategory) #当前目录下的网站 item['sites'] = [] for site in response.xpath('//section[@class="results sites"]//div[@class="site-item "]'): sites = {} sites['title'] = site.xpath('./div[@class="title-and-desc"]/a/div/text()').extract_first() sites['site_url'] = parse.urljoin(response.url, site.xpath('./div[@class="title-and-desc"]/a/@href').extract_first()) sites['description'] = site.xpath('.//div[@class="site-descr "]/text()').extract_first().strip() item['sites'].append(sites) yield item
-
爬取数据过程中遇到的问题
1.Curlie网站的反爬虫机制
Curlie网站为了防止爬虫抓取页面频率过快造成服务器瘫痪,设置了一个较低的抓取请求频率。即crawler程序两次进入站点时,以1秒为单位的最低延时。我们小组爬虫的时候,爬取100多条的数据的时候即被封了号,不能再爬取任何信息。需要过了几个小时才能重新抓取网页信息。
2.解析网页结构的时候发现,NEWS下面的部分网页内容是其他或者链接到其他目录的内容,例如News/By Subject/Accounting文件夹下面的url为https://curlie.org/Business/Accounting/News_and_Media,我们需要进行网页内容的筛选。
3.起始页面的sites没有爬取成功。我们小组分析是因为起始页面的通过parse函数解析,之后的页面才由rule规则指定的函数解析。因而起始页的sites没有爬取成功。 -
解决办法
1.反爬虫的解决办法
查询得知,当我们需要大量的爬取网站信息时,除了切换User-Agent之外,另外一个重要的方式就是设置IP代理,以防止我们的爬虫被拒绝。我们小组商量决定采取设置云服务器的IP代理。具体过程参考了http://www.cnblogs.com/cnkai/p/7401526.html 教程
2.采用rules来提取链接。利用allow中的正则表达式将位于news目录下面的url提取出来,同时结合restrict_xpaths,使用xpath表达式,和allow共同作用过滤链接。
3.针对起始页面的sites没有爬取成功,我们小组提出了两种解决思路。首先,将start_url修改为http://curlie.org/,修改Rule,让NEWS页面也能让回调函数解析,然后重新爬取数据。第二种是单独截取起始页的内容。考虑到爬取的页面较多,时间较长,因而我们小组选择第二种方法。
第二次截取页面内容的spider代码(curlie1.py)为:import scrapy from urllib import parse from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor from curlie.items import CurlieItem class CurlieSpider(CrawlSpider): name = "curlie1" allowed_domains = ["curlie.org"] start_urls = ["https://curlie.org/News/"] def parse(self,response): item = CurlieItem() #当前所属目录分类 item['category'] = response.url.lstrip('https://curlie.org') #当前目录下的网站 item['sites'] = [] for site in response.xpath('//section[@class="results sites"]//div[@class="site-item "]'): sites = {} sites['title'] = site.xpath('./div[@class="title-and-desc"]/a/div/text()').extract_first() sites['site_url'] = parse.urljoin(response.url, site.xpath('./div[@class="title-and-desc"]/a/@href').extract_first()) sites['description'] = site.xpath('.//div[@class="site-descr "]/text()').extract_first().strip() item['sites'].append(sites) yield item
6.实验结果
附代码及结果数据文件:curlie项目代码及结果数据文件(news.jl、curlie1.json)
7.其它尝试
除了上述的数据之外,我们通过变更Rule爬取了url中包含'News'的站点:
(变更后的Rule)——
Rule(LinkExtractor(allow=(r'[\s\S]*News[\s\S]*'),restrict_xpaths=('//section[@class="children"]//div[@class="cat-item"]')),callback="parse_item",follow=True),#url包含News的都要
详细结果文件见代码文件链接中的results.jl
下面是部分结果截图