一、实验目标
使用scrapy框架采集Curlie网站下Kids_and_Teens的分类子目录结构以及此分类下所收集的所有站点的网站名、网站链接及网站简介。
拟突破的重难点
- 破解网站的反爬虫策略
- 跟踪URL进行多层网页链接的爬取(crawlSpider或者递归)
- 使用pandas进行数据清洗与处理
二、实验环境
- 操作系统:CentOS 7.4 64位
- python环境:python2.7.5与python2.7.12两版本共存
- 采用的爬虫框架:scrapy1.5.0
三、实验方案设计
①分析目标网页,制定爬取规则----》②编写python代码----》③不断调试代码,得到初步结果----》④数据清洗,生成最终的结果文件
四、实验过程
1. 分析目标网页,制定爬取规则
此页面有14个子类,进入Arts分类网页。
爬取流程如下图,即爬取多层网页的结构数据。
2. 编写python代码
- items.py
# -*- coding: utf-8 -*-
import scrapy
class KidsItem(scrapy.Item):
cate_path = scrapy.Field()
sites = scrapy.Field()
在编写具体的爬虫代码时,本小组设计了两种方案。
第一种方案是在当网页
plan A:scrapy.Spider递归爬取网页数据
- kidsspiderA.py
# -*- coding: utf-8 -*-
# Please refer to the documentation for information on how to create and manage
# your spiders.
import scrapy
from kids.items import *
class Kids(scrapy.Spider):
name="kidsspider"
allowed_domains=["curlie.org"]
start_urls=[
'http://curlie.org/Kids_and_Teens/',
]
def parse(self,response):
item=KidsItem()
item['cate_path']=response.url.strip('http://curlie.org')
sites=response.xpath("//section[@class='results sites']").extract()
if sites:
sitelist=list()
for lp in response.xpath("//div[@id='site-list-content']/div"):
s={}
s['site_url']=lp.xpath("div[@class='title-and-desc']/a/@href").extract_first()
s['site_title']=lp.xpath("div[@class='title-and-desc']/a/div/text()").extract_first()
s['site_desc']=lp.xpath("div[@class='title-and-desc']/div[@class='site-descr ']/text()").extract_first().strip()
sitelist.append(s)
item['sites']=sitelist
yield item
for kids in response.xpath("//div[@id='subcategories-div']/section[@class='children']/div[@class='cat-list results leaf-nodes']/div"):
link=kids.xpath("a/@href").extract()
if link:
link="http://curlie.org" + link[0] #要爬取的下一个链接
yield scrapy.Request(link, callback=self.parse)
plan B 通过Crawlspider爬取网页
- kidsSpiderB.py
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from kids.items import *
class Kids(CrawlSpider):
name = 'kidsSpider3'
allowed_domains = ['curlie.org']
start_urls = ['http://curlie.org/Kids_and_Teens/']
rules = [
Rule(LinkExtractor(
restrict_css=('.cat-item')
), callback='parse_site', follow=True),
]
def parse_site(self, response):
item=KidsItem()
item['cate_path']=response.url.strip('http://curlie.org/')
sites=list()
for div in response.css('.title-and-desc'):
site={}
site['site_title']=div.css('.site-title::text').extract_first()
site['site_desc']= div.css('.site-descr::text').extract_first().strip()
site['site_url']=div.css('a::attr(href)').extract_first()
sites.append(site)
item['sites']=sites
yield item
3. 调试代码,得到初步结果
经过漫长的调试,最后我们的代码终于能够成功运行了。
晚上睡觉之前在创建的screen里面打开了爬虫,然后C-a d退出,将爬虫程序置于后台运行。
第二天八点起床的时候打开电脑,登录Xshell之后,输入
screen -r
,再次进入爬虫界面,发现kidsSpider已经finished了。嘻嘻~~~
-
plan A 爬取结果
可以看到爬取了7135条items
-
plan B爬取结果
可以看到一共爬取了6705条item,request请求的最大深度居然有16层。
两种方案爬取的结果不太一样,应该是因为使用的代理IP池不同,访问某些网页的时候被重定向的情况有所不同。
用editplus打开爬取的数据:
使用JsonViwer查看具体的层级结构:
经过浏览发现,cate_path的顺序是乱的,并且有一些重复的目录,需要进一步的数据清洗。
4. 数据清洗,得到最终结果
1)目标:去重、cate_path字段排序,导出cate_path字段。
2)工具: pandas库
3)清洗过程
-
安装pandas库
- 编写代码
import pandas as pd
df =pd.read_json('/home/lly/kids/kids_and_teens.json') # 读取json文件
print df.info()
#去重
df.duplicated("cate_path") #判断是否有重复项
df=df.drop_duplicates("cate_path") #去掉重复项
print df.info() #查看去重是否成功
#排序
df=df.sort_values(by=['cate_path']) #按照cate_path进行排序
df['cate_path'].to_csv('category.csv') #输入目录结构文件cate.csv
df.to_json('results.json')
4)清洗后的最终结果
一共去除了1022条重复数据,如下图:
爬取的目录结构,详见category.csv文件,部分截图如下:
爬取的网站信息,详见results.json文件,部分截图如下:
注:文件中的“0”、“1”、“2”....等是该site所在目录的索引号。
五、调试过程
1.如何利用递归遍历所有目录
curlie.org此网站下每个目录下都有很多层级子目录,由于要求爬取所有子目录及其目录下的sites信息。所以,需要用到递归的算法进行遍历。参考教程
存在一些目录下并没有sites,因此对sites进行了if判断。并且对sites进行整合,将一个目录下的sites整合到一个list()中。改进后的代码见下图:
2.破解目标网站的反爬虫机制
早就听闻,爬虫-反爬虫-反反爬虫....是爬虫蜘蛛与网站运维人员之间的一场永无止境的斗争。这一次,终于遇上了。
curlie的反爬虫机制有封禁IP、服务器端网页重定向等,在我们的本次实验中遇到的问题有:
- TCP connection timed out: 110:
- Connection was refused by other side: 111: Connection refused.
- DEBUG: Redirecting (301)永久重定向
- DEBUG: Redirecting (302)暂时重定向
- .......
为了解决上述问题,本小组采取的解决办法有:
- 设置随机user-agent,伪造user发送请求
- 设置随机IP代理
- 设置下载延迟
- 设置不遵循robots.txt协议
- 禁用cookie
六、爬虫策略的进一步探索
- 实验的不足之处:不知道是否已经目标目录下的所有的站点信息;网站的目录结构存成json或者xml的树状结构更能体现目录之间的层级关系。
为了解决上述问题,小组进行了爬虫策略的进一步探索。
1.用crontab实现定时执行scrapy任务
crontab命令常见于Unix和类Unix的操作系统之中,用于设置周期性被执行的指令。下面是使用crontab进行定时循环执行scrapy爬虫的操作:
安装crontab
yum install crontab
编辑crontab服务文件
crontab -e
(其他参数:-u 指定用户下的crontab; -r 删除; -l 查看crontab工程目录; -i 有提示的删除)在crontab文件中添加命令以爬取name为ACW1的爬虫
* * * * * cd /home/hyj/kids && scrapy crawl ACW1 -o ACW1.json
详细写法参见Linux下的crontab定时执行任务命令详解
以上指令可以实现每分钟执行一次爬虫。但是在爬虫比较多时,可以采用shell脚本集中管理。
若爬虫运行时间较长,又想错开目标网站的访问高峰时间段,可结合scrapy jobs进行暂停爬取,之后再恢复运行,接着上一次的断点继续爬取数据。
遇到的一些问题
如果直接在crontab -e中输入* * * * * scrapy crawl xxx
,定时任务是不会生效的,因为我们不知道crontab执行时,其所处的目录,很有可能就没有scrapy命令和crawl命令。因此,改用指令:* * * * * cd /home/hyj/kids && scrapy crawl ACW1
就可以正常的执行定时任务了。-
查看crontab历史执行情况
默认情况下,crontab中执行的日志写在/var/log下。可通过以下指令在root权限下访问:
tail –f /var/log/cron
2.爬取curlie全站的策略
-
分析网站首页,改写爬虫代码
可以看到首页上的打开分类目录的下一层链接都在名为"top cat"的标签里面,之后每一个大类的网站结构都跟kids_and_teens的结构一样。因此,只要在crawlSpider的rules对象中将网站url的匹配规则改写一下就可以了。代码改动部分有一下两处:
-
爬虫的执行
在首页上可以看到curlie收录的站点约370万个,分别位于约100万个目录下。
在没有网站的反爬虫限制,用一台服务器执行爬虫指令,遵循crawl_delay=1的协议,一切顺利的话,大约17天能爬下来所有的站点数据。
3.分布式爬虫spider-redis
为了满足在较短时间内爬取大量数据的需求,分布式的爬虫应运而生,在爬取curlie全站的时候也可以采用分布式的爬虫。要搭建分布式爬虫需要多台服务器:
- 一台中心服务器master:不执行具体的爬虫任务,只负责request任务分配、将数据存入redis数据库、判断url是否重复等工作
- 多台爬虫执行服务器Slaver:负责执行爬虫程序,运行过程中提交新的Request给中心服务器
Slaver从中心服务器拿任务(Request、url)进行数据抓取,在抓取数据的同时,产生新任务的Request便提交给 Master 处理;
Master端只有一个Redis数据库,负责将未处理的Request去重和任务分配,将处理后的Request加入待爬队列,并且存储爬取的数据。