目录
1.日志中模拟RetryMiddleware工作方式做异常处理
1.《在Scrapy中使用Twisted 的 defer.inlineCallbacks的时的说明》
2.《[Scrapy使用技巧] 如何在scrapy中捕获并处理各种异常》
一、任务描述
1.目标
此次爬虫项目的任务目标为爬取公考雷达网站首页顶部链接中的各个职位概括信息以及地点内容。
2.网站特点
观察网页特点一般使用浏览器开发者工具,观察网页获取请求的方式以及网页是静态或动态,存放数据的具体位置等等。通过浏览器开发者工具观察网页特点如下图,网页为静态网页,请求方式为GET请求,编码为utf-8编码,网站为静态网站,要提取的数据在html中都有显示。
二、技术需求
1.使用技术需求
此次爬虫项目涉及到的技术需求有:scrapy框架、python、pyCharm编译环境、LinkExtractor链接提取器、scrapy爬取规则Rule,Twister.defer
2.输入输出需求
爬取的初级url进行控制台输出与存入数据库,次级url进行控制台输出爬取的职位详细信息存入数据库。
3.数据库存放需求
理想的数据存放方式为存入数据库中,招收地(或单位)、职位性质、职位内容三列
入mongdb非关系型数据库。
4.异常值处理需求
中间件模拟scrapy日志自动捕获异常,Spider中对获取response进行异常值处理,标签异常值处理可以省略
三、运行结果及说明
(一)确定数据源
1.确定外链数据源及内链具体位置
此次项目爬取的网站为公考雷达网站(https://www.gongkaoleida.com/),网站主要作用为汇总全国的事业编、公务员等职位信息。
使用浏览器开发者工具定位元素
2.确定内链数据源及最终爬取内容具体位置
此次爬取分为了三层递进爬取,在外链中获取到的内链中获取最终爬取内容页面的链接信息。
最终爬取内容的位置
3.新建项目
新建scrapy项目
新建spider文件
(二)确定获取方式
1.巧用LinkExtractor获取内链
scrapy.linkextractors模块中提供了与Scrapy捆绑在一起的链接提取器类,通过链接提取器提取出来url可以直接存入item传入下级爬取,在本项目中使用了restrict_xpaths,restrict_xpaths是一个XPath(或XPath的列表),它定义响应中应从中提取链接的区域。如果给出,只有那些XPath选择的文本将被扫描链接。
在shell中尝试获取
实现到代码中
2.在xpath获取职位性质
在Shell中尝试获取
实现到代码中
(三)实现数据去重
1.应用Crawling rules实现url去重
在爬取最终提取信息的内链链接时,采用的是提取最下方的页面链接传递给下级进行爬取,在最下方的页面链接跳转出使用链接提取器获取会出现链接重复的问题,‘下一页’提取出的链接会与第二页重复,‘尾页提取出来的链接会与最后一页的链接重复,方便快捷的同时应该做到避免重复,所以应用Crawling rules实现url去重,从而避免数据的重复爬取。
代码实现
(四)数据去重
1.url数据控制台输出
在控制台输出url数据,外链url数据存入mongdb,内链多层的url数据在控制台输出,成功获得请求的有Request successfully
2.外链url与最终职位信息存入mongodb
将所需要的信息通过item文件、管道文件存入mongodb非关系型数据库,这样可以导出txt文件或者csv文件。
(五)异常值处理
1.日志中模拟RetryMiddleware工作方式做异常处理
参考《如何在scrapy中捕获并处理各种异常》一文,制定一个中间件异常处理模块模拟日志中RetryMiddleware工作方式,将url更改为错误url,测试中间件是否能捕获异常。
未开启中间件时:
开启中间件后:
中间件的部分代码展示
2.Spider中根据response响应做异常处理
在spider文件中做response异常处理,专设一个Erroritem存放错误信息参数
设置错误url试运行代码
设置超时url试运行代码
(六)制定反反爬策略
1.Robot协议
将settings文件中的robot协议解放为初级反爬策略
2.制定虚假UA
在settings文件中设置一个虚假的Use-Agent,在python中的UserAgent库中有一个fake_useragent模块可以生成虚假use-Agent,再用random函数随机生成虚假use-Agent。
3.伪装UA
同时要伪装Use-Agent,爬虫在发送请求与获取响应就是模拟浏览器工作发送请求,获取响应,因此将cookie信息,accept信息等放入随机生成的虚假Use-Agent中伪装成浏览器的样子。
(七)运行结果展示
1.控制台展示
2.数据库展示
四、遇见的困难以及解决方案
1.难点一,链接提取
在链接提取中,一开始时尝试xpath提取,后来接触到链接采集器,去重又成为了问题,以及最重要的链接采集器爬取到的数据无法与xpath提取的另外两个特征属性放在同一处,国家、国企这两块区域的信息无法与职位详细信息做到对应存储,尝试过写循环的方式将xpath提取到的信息循环放入链接提取器中,然而链接提取器无法放入循环,对链接提取器的工作环境不太明确。
解决办法:这个尝试了多种循环,最终也没有很完美的解决,是在最终结果中只能将xpath提取到的数据存在链接提取器顶部
2.难点二,递进爬取
此次项目分为三次递进爬取,在spider爬虫主文件中分为了三个函数进行递进爬取,parse,del_parse,dell_parse三个文件进行分步式递进爬取,url内外链如何在scrapy中进行传递与响应是一个需要解决的问题。解决办法:scrapy中有scrapy.request模块可以承接参数,并且将参数转去设定的函数中。
3.难点三,数据存储
再次了解到mongdb的多表存入,理论上mongdb是支持多表存入的,但是在存入时,只能存入一个表,将存入表一的代码注释,表二对应的数据就会存入表二,因此推断出存入的数据是没有出错的,但是将注释取消后,对应表一的数据存入表一,表二数据无法存入,经过多次尝试,发现多个Pipeline管道类文件似乎与mongdb中的多表冲突,即使区分item 类也无法进行多表存入。
解决办法:使用一个Pipeline管道类文件判断item类进行多表存入就可以成功
五、参考资料
1.《在Scrapy中使用Twisted 的 defer.inlineCallbacks的时的说明》
在Scrapy中使用Twisted 的 defer.inlineCallbacks的时的说明_小白的博客-CSDN博客
2.《[Scrapy使用技巧] 如何在scrapy中捕获并处理各种异常》
[Scrapy使用技巧] 如何在scrapy中捕获并处理各种异常_Rei的博客-CSDN博客_scrapy异常处理
六、源码
1.Settings文件
添加:LOG_LEVEL = "WARNING"#取消日志在控制台输出
更改:
2.Spider文件
import scrapy
from kaogong.items import KaogongItem,ZhiweiItem,ErrorItem,ZhiweiurlItem
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import Rule
class GongkaozhiweiSpider(scrapy.Spider):
name = 'gongkaozhiwei'
allowed_domains = ['www.gongkaoleida.com']
start_urls = ['https://www.gongkaoleida.com/']
url = []
url2 = []
url3 = []
#初级爬取
def parse(self, response):
#异常值处理(响应异常)
item0 = ErrorItem()
if not response.url: # 接收到url==''时
print('URLError:500')
item0['key'] = {'key': response.meta, '_str': '500', 'alias': ''}
yield item0
elif 'exception' in response.url:
print('Sorry exception')
item0['key'] = {'key':response.meta,'_str':'EXCEPTION','alias':''}
yield item0
else:
print('Request successfully')
#利用scrapy链接提取器
#提取内链url
link = LinkExtractor(restrict_xpaths='/html/body/section/div[1]/div[*]/ul/li[*]/span[1]/a')
links = link.extract_links(response)
print(links)#输出提取到的
#循环检索内链
for link in links:
#输出内链的url以及内链信息
print(link.url, link.text)
#将内链追加为列表
self.url.append(link.url)
#定义item
#将数据内容装入item
item = KaogongItem()
item['url'] = link.url
item['url_text'] = link.text
#传入item
yield item
#传入下一级爬取
yield scrapy.Request(url=item['url'],callback=self.del_parse)
def del_parse(self, response):
#异常值处理
item0 = ErrorItem()
if not response.url: # 接收到url==''时
print('URLError:500')
item0['key'] = {'key': response.meta, '_str': '500', 'alias': ''}
yield item0
#其他错误
elif 'exception' in response.url:
print('Sorry exception')
item0['key'] = {'key':response.meta,'_str':'EXCEPTION','alias':''}
yield item0
else:
print('Request successfully')
# 定义每个页码的规则,用于提取链接
#直接进行链接提取出现重复,使用rule匹配规则
link2 = LinkExtractor(allow=r'page=\d+')
rules = (
# 实例化了一个规则解析器对象
#follow实际上就为数据的去重
Rule(link2, follow=True),
)
links2 = link2.extract_links(response)
if link2 != []:
for link in links2:
print(link.url)
self.url2.append(link.url)
# 定义item2
item2 = ZhiweiurlItem()
item2['del_url'] = link.url
yield item2
# 传入下下级爬取信息
yield scrapy.Request(url=item2['del_url'], callback=self.dell_parse)
# 第三层内容爬取(职位详细介绍)
else:
link3= LinkExtractor(restrict_xpaths='/html/body/section/div[4]/div/div/div[1]/ul/li[*]/h5/a')
links3 = link3.extract_links(response)
item3 = ZhiweiItem()
item3['details'] = response.url
item3['Unit_address']= response.xpath("/html/body/section/div[4]/div/div/div[1]/ul/li[*]/h5/i[1]/text()").extract()
item3['Unit_nature']= response.xpath("/html/body/section/div[4]/div/div/div[1]/ul/li[*]/h5/i[2]/text()").extract()
yield item3
for link in links3:
item3 = ZhiweiItem()
self.url3.append(link.url)
item3['details'] = link.text
yield item3
def dell_parse(self, response):
# 利用mongdb继续存储的性质将翻页的链接提取内容按照同样方式存入数据库
link3 = LinkExtractor(restrict_xpaths='/html/body/section/div[4]/div/div/div[1]/ul/li[*]/h5/a')
links3 = link3.extract_links(response)
item3 = ZhiweiItem()
item3['details'] = response.url
item3['Unit_address'] = response.xpath("/html/body/section/div[4]/div/div/div[1]/ul/li[*]/h5/i[1]/text()").extract()
item3['Unit_nature'] = response.xpath("/html/body/section/div[4]/div/div/div[1]/ul/li[*]/h5/i[2]/text()").extract()
yield item3
for link in links3:
item3 = ZhiweiItem()
self.url3.append(link.url)
item3['details'] = link.text
yield item3
3.Items文件
import scrapy
class KaogongItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
link_url = scrapy.Field()
url = scrapy.Field()
url_text = scrapy.Field()
del_url = scrapy.Field()
detail_url = scrapy.Field()
pass
class ZhiweiurlItem(scrapy.Item):
del_url = scrapy.Field()
pass
class ZhiweiItem(scrapy.Item):
details = scrapy.Field()
Unit_address = scrapy.Field()
Unit_nature = scrapy.Field()
pass
class ErrorItem(scrapy.Item):
key = scrapy.Field()
pass
4.Pipelines文件
import pymongo
from scrapy.utils.project import get_project_settings
from kaogong.items import KaogongItem, ZhiweiItem,ErrorItem,ZhiweiurlItem
settings = get_project_settings()
class KaogongPipeline(object):
@classmethod
def __init__(self):
# 获取setting主机名、端口号和数据库名称
settings = get_project_settings()
host = settings['MONGODB_HOST']
port = settings['MONGODB_PORT']
dbname = settings['MONGODB_DBNAME1']
# 创建数据库连接
client = pymongo.MongoClient(host=host, port=port)
# 指向指定数据库
mdb = client['kaogong']
# 获取数据库里面存放数据的表名
self.post1 = mdb[settings['MONGODB_DOCNAME1']]
self.post2 = mdb[settings['MONGODB_DOCNAME2']]
def process_item(self, item, spider):
if isinstance(item, KaogongItem): # 对item的class进行鉴别
data = dict(item)
# 向指定的表里添加数据
self.post1.insert(data)
return item
elif isinstance(item, ZhiweiItem):
data = dict(item)
# 向指定的表里添加数据
self.post2.insert(data)
return item
elif isinstance(item, ErrorItem):
print(item)
return item
elif isinstance(item, ZhiweiurlItem):
print(item)
return item
5.Middlewares文件
from twisted.internet import defer
from twisted.internet.error import TimeoutError, DNSLookupError, \
ConnectionRefusedError, ConnectionDone, ConnectError, \
ConnectionLost, TCPTimedOutError
from scrapy.http import HtmlResponse
from twisted.web.client import ResponseFailed
from scrapy.core.downloader.handlers.http11 import TunnelError
class ProcessAllExceptionMiddleware(object):
ALL_EXCEPTIONS = (defer.TimeoutError, TimeoutError, DNSLookupError,
ConnectionRefusedError, ConnectionDone, ConnectError,
ConnectionLost, TCPTimedOutError, ResponseFailed,
IOError, TunnelError)
def process_response(self,request,response,spider):
#捕获状态码为40x/50x的response
if str(response.status).startswith('4') or str(response.status).startswith('5'):
#随意封装,直接返回response,spider代码中根据url==''来处理response
response = HtmlResponse(url='')
return response
#其他状态码不处理
return response
def process_exception(self,request,exception,spider):
#捕获几乎所有的异常
if isinstance(exception, self.ALL_EXCEPTIONS):
#在日志中打印异常类型
print('Got exception: %s' % (exception))
#随意封装一个response,返回给spider
response = HtmlResponse(url='exception')
return response
6.运行文件
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
settings = get_project_settings()
crawler = CrawlerProcess(settings)
crawler.crawl('gongkaozhiwei')
crawler.start()