
公众号:uncle39py
2022/10/12阅读:19主题:默认主题
使用CrawlSpider爬取拉勾网所有的职位信息超少代码量
爬取拉勾网所有的职位信息
副标题:scrapy中CrawlSpider的使用
视频教程在此 https://mp.weixin.qq.com/s/DG-T965Y9yKLwNYPus2cXA

上一节中爬取知乎全站的问答,需要自己从知乎首页开始提取所有的url,如果是问答的url则yield出去解析,否则继续爬取此url下的所有url
另外在每一个具体问答的页面也提取所有的url来解析其中的关于问答的url,然后这样不断循环就能爬取到所有的问答链接.
这种使用Spider的方式需要自己手动发送请求.
scrapy框架中的CrawSpider则可以仅仅只设置url条件,满足就可以自动发生请求下载页面.
CrawSpider是Sipder的子类
和之前创建 spider 一样,虽然可以在创建 Scrapy 项目之后手动构造 spider,但是 Scrapy 也给出了在终端下创建 CrawlSpider 的指令:
scrapy genspider -t crawl 爬虫名 域名
首先,要介绍LinkExtractor(链接提取器)
CrawlSpider 与 spider 不同的是就在于下一次请求的 url 不需要自己手动解析并发送请求下载,而这一点则是通过LinkExtractor实现的,LinkExtractor定义了一些提取链接的规则,比如拉勾网中职位信息的链接规则,不是此链接就不会去提取。
LinkExtractor是一个类,实例化传入的参数如下:
-
allow:允许的 url。所有满足这个正则表达式的 url 都会被提取 -
deny:禁止的 url。所有满足这个正则表达式的 url 都不会被提取 -
allow_domains:允许的域名。只有在这个里面指定的域名的 url 才会被提取 -
deny_domains:禁止的域名。所有在这个里面指定的域名的 url 都不会被提取 -
restrict_xpaths:严格的 xpath。和 allow 共同过滤链接
Rule(规则)
从宏观角度来考虑,一个完整的规则包含:
提取链接的规则---LinkExtractor
包含下载后的回调函数---callback
包含下载的页面中如果有符合的LinkExtractor规则的链接,是否要跟进继续提取---follow
所以在使用CrawSpider时,主要做的事情就是设置rules,如下:
class LagouSpider(CrawlSpider):
name = 'lagou'
allowed_domains = ['www.lagou.com']
start_urls = ['https://www.lagou.com/zhaopin/Java/?labelWords=label']
#提取以下allow的链接,交给回调函数parse_job()解析,然后每一个页面都跟进提取
rules = (
Rule(LinkExtractor(allow=r'jobs/\d+.html'), callback='parse_job', follow=True),
)
设置好rules后,我们只要给相应的url写回调函数提取想要的元素即可
def parse_job(self, response):
#解析拉勾网的职位
item_loader = LagouJobItemLoader(item=LagouJobItem(), response=response)
item_loader.add_css("title", ".job-name::attr(title)")
item_loader.add_value("url", response.url)
item_loader.add_value("url_object_id", get_md5(response.url))
item_loader.add_css("salary", ".job_request .salary::text")
item_loader.add_xpath("job_city", "//*[@class='job_request']//span[2]/text()")
item_loader.add_xpath("work_years", "//*[@class='job_request']//span[3]/text()")
item_loader.add_xpath("degree_need", "//*[@class='job_request']//span[4]/text()")
item_loader.add_xpath("job_type", "//*[@class='job_request']//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()
return job_item
当然,之前的基建操作要先做,比如建数据库,Item,ItemLoader,以及模拟登陆的环节,这些跟前面章节的方式一致,以下代码中有一些细节,是针对提取数据的处理,不影响对大的架构的理解
以下为item,itemloader
class LagouJobItemLoader(ItemLoader):
#自定义itemloader
default_output_processor = TakeFirst()
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(
input_processor=MapCompose(remove_tags, handle_jobaddr),
)
company_name = scrapy.Field()
company_url = scrapy.Field()
tags = scrapy.Field(
input_processor = Join(",")
)
crawl_time = scrapy.Field()
def get_insert_sql(self):
insert_sql = """
insert into lagou_job(title, url, url_object_id, salary, job_city, work_years, degree_need,
job_type, publish_time, job_advantage, job_desc, job_addr, company_name, company_url,
tags, crawl_time) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE salary=VALUES(salary), job_desc=VALUES(job_desc)
"""
params = (
self.get("title", ""),
self.get("url", ""),
self.get("url_object_id", ""),
self.get("salary", ""),
self.get("job_city", ""),
self.get("work_years", ""),
self.get("degree_need", ""),
self.get("job_type", ""),
self.get("publish_time", "0000-00-00"),
self.get("job_advantage", ""),
self.get("job_desc", ""),
self.get("job_addr", ""),
self.get("company_name", ""),
self.get("company_url", ""),
self.get("job_addr", ""),
self["crawl_time"].strftime(SQL_DATETIME_FORMAT),
)
return insert_sql, params
入库操作同样使用异步的pipeline,不需要做任何的变动
class MysqlTwistedPipline(object):
def __init__(self, dbpool):
self.dbpool = dbpool
@classmethod
def from_settings(cls, settings):
dbparms = dict(
host = settings["MYSQL_HOST"],
db = settings["MYSQL_DBNAME"],
user = settings[" "],
passwd = settings["MYSQL_PASSWORD"],
charset='utf8',
cursorclass=MySQLdb.cursors.DictCursor,
use_unicode=True,
)
dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms)
return cls(dbpool)
def process_item(self, item, spider):
#使用twisted将mysql插入变成异步执行
query = self.dbpool.runInteraction(self.do_insert, item)
query.addErrback(self.handle_error, item, spider) #处理异常
return item
def handle_error(self, failure, item, spider):
#处理异步插入的异常
print (failure)
def do_insert(self, cursor, item):
#执行具体的插入
insert_sql,params = item.get_insert_sql()
cursor.execute(insert_sql, tuple(params))
还有一些细节,比如settings.py中的常规设置,包括不限于一下:
-
ROBOTSTXT_OBEY:设置为 False,否则为 True。True 表示遵守机器协议,此时爬虫会首先找 robots.txt 文件,如果找不到则会停止 -
DEFAULT_REQUEST_HEADERS:默认请求头,可以在其中添加 User-Agent,表示该请求是从浏览器发出的,而不是爬虫 -
DOWNLOAD_DELAY:表示下载的延迟,防止过快 -
ITEM_PIPELINES:启用 pipelines.py
作者介绍
