公众号:uncle39py

V1

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

分类:

后端

标签:

后端

作者介绍

公众号:uncle39py
V1