一,前言
之前利用scrapy爬取伯乐在线,知乎时是用到scrapy的basic模板,即Spider,这次用CrawlSpider来爬取拉勾网的招聘信息
CrawlSpider基于Spider,但是可以说是为全站爬取而生,是目前最流行的scrapy模板
二,创建工程
在cmd中cd进入项目的目录,然后输入:scrapy gensipder -t crawl lagou www.lagou.com
默认生成:
三,简要说明
CrawlSpider是爬取那些具有一定规则网站的常用的爬虫,它基于Spider并有一些独特属性,也就是之前spider的爬取规则现在也适用,下面的参数可以按ctrl点击CrawlSpider获取源码
- rules: 是Rule对象的集合,用于匹配目标网站并排除干扰
- parse_start_url: 用于爬取起始响应,必须要返回Item,Request中的一个。
因为rules是Rule对象的集合,所以这里也要介绍一下Rule对象
它有几个参数:linkExtractor、callback=None、cb_kwargs=None、follow=None、process_links=None、process_request=None
- callback为回调函数,传入一个str,即回调函数的名字
- follow为boolean类型,为true就继续跟踪,为false则不往下跟踪
其中的linkExtractor既可以自己定义,也可以使用已有LinkExtractor类,主要参数为:
- allow:满足括号中“正则表达式”的值会被提取,如果为空,则全部匹配。
- deny:与这个正则表达式(或正则表达式列表)不匹配的URL一定不提取。
- allow_domains:会被提取的链接的domains。
- deny_domains:一定不会被提取链接的domains。
- restrict_xpaths:使用xpath表达式,和allow共同作用过滤链接。还有一个类似的restrict_cs
4.1 rule的编写
输入拉勾网首页:https://www.lagou.com/
观察职位的url:https://www.lagou.com/jobs/3157876.html 为https://www.lagou.com/jobs/\d+.html 格式
此外我们在进入职位url之前必须从 https://www.lagou.com/zhaopin/Android/ ,https://www.lagou.com/gongsi/j118082.html之类的url进入,所以匹配https://www.lagou.com/zhaopin/* https://www.lagou.com/gongsi/j\d+
4.2 parse_item的编写
这里不详细分析了,在如图一个个用css,或者xpath分析
具体代码如下:
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ArticleSpider.items import FirstItemLoader, LagouJobItem
class LagouSpider(CrawlSpider):
name = 'lagou'
allowed_domains = ['www.lagou.com']
start_urls = ['https://www.lagou.com/zhaopin/Python/']
rules = (
Rule(LinkExtractor(allow=("zhaopin/.*",)),follow=True),
Rule(LinkExtractor(allow=("gongsi/j\d+.html",)), follow=True),
Rule(LinkExtractor(allow=r'jobs/\d+.html'), callback='parse_job', follow=True),
)
def parse_job(self, response):
#解析拉勾网的职位
# pass
# #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
# #i['name'] = response.xpath('//div[@id="name"]').extract()
# #i['description'] = response.xpath('//div[@id="description"]').extract()
item_loader = FirstItemLoader(item=LagouJobItem(),response=response)
item_loader.add_css("title", ".job-name::attr(title)")
item_loader.add_value("url", response.url)
item_loader.add_css("salary", ".job_request .salary::text")
item_loader.add_xpath("job_city", "//*[@class='job_request']/p/span[2]/text()")
item_loader.add_xpath("work_years", "//*[@class='job_request']/p/span[3]/text()")
item_loader.add_xpath("degree_need", "//*[@class='job_request']/p/span[4]/text()")
item_loader.add_xpath("job_type", "//*[@class='job_request']/p/span[5]/text()")
item_loader.add_css("tags", '.position-label li::text')
item_loader.add_css("publish_time", ".publish_time::text")
item_loader.add_css("job_advantage", ".job-advantage p::text")
item_loader.add_css("job_desc", ".job_bt div")
item_loader.add_css("job_addr", ".work_addr")
item_loader.add_css("company_name", "#job_company dt a img::attr(alt)")
item_loader.add_css("company_url", "#job_company dt a::attr(href)")
# item_loader.add_value("crawl_time", datetime.now())
job_item = item_loader.load_item()
# print(job_item)
yield job_item
pass
其中items代码如下:
import re
import scrapy
from scrapy.loader.processors import TakeFirst,MapCompose,Join
from scrapy.loader import ItemLoade
class FirstItemLoader(ItemLoader):
#自定义itemloader,继承scrapy的ItemLoader类
default_output_processor = TakeFirst()
def remove_splash(value):
#去掉工作城市的斜线
return value.replace("/","")
class LagouJobItem(scrapy.Item):
#拉勾网职位信息
title = scrapy.Field()
url = scrapy.Field()
url_object_id = scrapy.Field()
salary = scrapy.Field()
job_city = scrapy.Field(
input_processor=MapCompose(remove_splash),
)
work_years = scrapy.Field(
input_processor = MapCompose(remove_splash),
)
degree_need = scrapy.Field(
input_processor = MapCompose(remove_splash),
)
job_type = scrapy.Field()
publish_time = scrapy.Field()
job_advantage = scrapy.Field()
job_desc = scrapy.Field()
job_addr = scrapy.Field(
)
company_name = scrapy.Field()
company_url = scrapy.Field()
tags = scrapy.Field(
input_processor = Join(",")
)
crawl_time = scrapy.Field()
运行结果:
如果没有爬取成功提示要登录,可以看看下面的应对措施
五,应对反爬措施
由于拉勾的反爬措施比较严格,所以可以会需要登录才可以访问,这里采取了反反爬虫的方法有:
5.1,设置随机的user-agent
首先导入 fake_useragent :pip install fake_usergent
然后在中间件 middlewares.py 添加以下代码:
from fake_useragent import UserAgent
这样每次下载页面时就会传入不同的 user-agent#随机更换user-agent class RandomUserAgentMiddlware(object): def __init__(self,crawler): super(RandomUserAgentMiddlware, self).__init__() self.ua = UserAgent() @classmethod def from_crawler(cls,crawler): return cls(crawler) def process_request(self,request,spider): request.headers.setdefault('User-Agent',self.ua.random)
别忘了在setting添加middlewares设置,否则该设置不生效
DOWNLOADER_MIDDLEWARES = { 'ArticleSpider.middlewares.RandomUserAgentMiddlware': 543, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, }
5.2,设置下载延迟
在setting中添加设置:
DOWNLOAD_DELAY = 10
这样基本就能爬取拉勾网大部分招聘信息了,如果还是不行,还能采取selenium驱动谷歌浏览器来访问,还可以设置自动登录,或者自动修改ip来解决,这里就不一一列举
六,问题
CrawlSpider如何工作的?
因为CrawlSpider继承了Spider,所以具有Spider的所有函数。 首先由
start_requests
对start_urls
中的每一个url发起请求(make_requests_from_url
),这个请求会被parse接收。在Spider里面的parse需要我们定义,但CrawlSpider定义parse
去解析响应(self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)
) _parse_response根据有无callback
,follow
和self.follow_links
执行不同的操作def _parse_response(self, response, callback, cb_kwargs, follow=True): ##如果传入了callback,使用这个callback解析页面并获取解析得到的reques或item if callback: cb_res = callback(response, **cb_kwargs) or () cb_res = self.process_results(response, cb_res) for requests_or_item in iterate_spider_output(cb_res): yield requests_or_item ## 其次判断有无follow,用_requests_to_follow解析响应是否有符合要求的link。 if follow and self._follow_links: for request_or_item in self._requests_to_follow(response): yield request_or_item
其中
_requests_to_follow
又会获取link_extractor
(这个是我们传入的LinkExtractor)解析页面得到的link(link_extractor.extract_links(response))
,对url进行加工(process_links,需要自定义),对符合的link发起Request。使用.process_request
(需要自定义)处理响应。