爬取拉钩网

数据预览

编写lagou.py

文件位于 LagouSpider/spiders/lagou.py

导包

import scrapy
import pickle
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import LagouJobItemLoader, LagouJobItem
from LagouSpdier.utils.common import get_md5
from datetime import datetime
from ..settings import BASE_DIR
import os

首先思考我么要爬取哪些位置

可以通过 Rule 来限制我们要爬取的域

class LagouSpider(CrawlSpider):
    name = 'lagou'
    allowed_domains = ['www.lagou.com']
    start_urls = ['https://www.lagou.com/']

    rules = (
        Rule(LinkExtractor(allow='zhaopin/.*'), follow=True),
        Rule(LinkExtractor(allow='zhaopin/jiqishijue/.*'), follow=True),
        Rule(LinkExtractor(allow='zhaopin/shenduxuexi/.*'), follow=True),
        Rule(LinkExtractor(allow='jobs/list_计算机视觉/.*'), follow=True),
        Rule(LinkExtractor(allow='zhaopin/tuxiangchuli/.*'), follow=True),
        Rule(LinkExtractor(allow='zhaopin/Python/.*'), follow=True),
        Rule(LinkExtractor(allow='zhaopin/ziranyuyanchuli/.*'), follow=True),
        Rule(LinkExtractor(allow='zhaopin/tuxiangshibie/.*'), follow=True),
        #Rule(LinkExtractor(allow='gongsi/\d+.html'), follow=True),
        Rule(LinkExtractor(allow=r'jobs/\d+.html'), callback='parse_item', follow=True),
    )

通过chromedriver.exe来获取cookies

爬取拉钩尽量每次带上cookies,于是我们用chromedriver来模拟登陆一次,然后把cookies保存到本地。

def start_requests(self):
        # 去使用selenium模拟登录后拿到cookie交给scrapy的request使用
        # 1、通过selenium模拟登录
        # 从文件中读取cookies
        cookies = []
        if os.path.exists(BASE_DIR + "/cookies/lagou.cookie"):
            cookies = pickle.load(open(BASE_DIR + "/cookies/lagou.cookie", "rb"))

        if not cookies:
            from selenium import webdriver
            browser = webdriver.Chrome(executable_path="C:/chromedriver/80.0.3987.106/chromedriver.exe")
            browser.get("https://passport.lagou.com/login/login.html")
            browser.find_element_by_css_selector(".form_body .input.input_white").send_keys("你的用户名")
            browser.find_element_by_css_selector('.form_body input[type="password"]').send_keys("你的密码")
            browser.find_element_by_css_selector('div[data-view="passwordLogin"] input.btn_lg').click()
            import time
            time.sleep(15)
            cookies = browser.get_cookies()
            # 写入cookie到文件中
            pickle.dump(cookies, open(BASE_DIR + "/cookies/lagou.cookie", "wb"))

        cookie_dict = {}
        for cookie in cookies:
            cookie_dict[cookie["name"]] = cookie["value"]

        for url in self.start_urls:
            yield scrapy.Request(url, dont_filter=True, cookies=cookie_dict)

找到需要爬取的xpath 或者selecter

以下是我需要的内容

def parse_item(self, response):
        if response.status == 302:
            return
        # 解析拉勾网的职位
        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_css("job_city", ".job_request > h3 > span:nth-child(2)::text")
        item_loader.add_css("work_years", ".job_request > h3 > span:nth-child(3)::text")
        item_loader.add_css("degree_need", ".job_request > h3 > span:nth-child(4)::text")
        item_loader.add_css("job_type", ".job_request > h3 > span:nth-child(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

编写 items.py

导包

import scrapy
from w3lib.html import remove_tags
from scrapy.loader.processors import MapCompose, TakeFirst, Join
from scrapy.loader import ItemLoader
from .settings import SQL_DATETIME_FORMAT,SQL_DATE_FORMAT

from .models.es_types import ArticleType
from elasticsearch_dsl.connections import connections
es = connections.create_connection(ArticleType._doc_type.using)

编写对数据处理的方法

def remove_cn(value):
    #去掉 发布于拉勾网
    return value.replace("发布于拉勾网","")


def handle_jobaddr(value):
    addr_list = value.split("\n")
    addr_list = [item.strip() for item in addr_list if item.strip()!="查看地图"]
    return "".join(addr_list)

自定义itemloader

class LagouJobItemLoader(ItemLoader):
    #自定义itemloader
    default_output_processor = TakeFirst()

编写lagouitem

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(
        input_processor=MapCompose(remove_cn),
    )
    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为空的情况,需要解决
    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",0), self.get("url",0), self.get("url_object_id",0),
            self.get("salary",0), self.get("job_city",0),
            self.get("work_years",0), self.get("degree_need",0), self.get("job_type",0),
            self.get("publish_time",0), self.get("job_advantage",0), self.get("job_desc",0),
            self.get("job_addr",0), self.get("company_name",0), self.get("company_url",0),
            self.get("tags",0), self["crawl_time"].strftime(SQL_DATETIME_FORMAT),
        )

        return insert_sql, params

    # 用于保存到ElasticSearch  可以不用
    def save_to_es(self):
        article = ArticleType()
        article.title = self.get("title",0)
        article.url =  self.get("url",0)
        article.url_object_id = self.get("url_object_id",0)
        article.salary = self.get("salary",0)
        article.job_city = self.get("job_city",0)
        article.work_years = self.get("work_years",0)
        article.degree_need = self.get("degree_need",0)
        article.job_type = self.get("job_type",0)
        article.publish_time = self.get("publish_time",0)
        article.job_advantage = self.get("job_advantage",0)
        article.job_desc = self.get("job_desc",0)
        article.job_addr = self.get("job_addr",0)
        article.company_name = self.get("company_name",0)
        article.company_url = self.get("company_url",0)
        article.tags =  self.get("tags",0)
        article.crawl_time = self["crawl_time"].strftime(SQL_DATETIME_FORMAT)

        article.suggest = gen_suggests(ArticleType._doc_type.index, ((article.tags, 9), (article.title, 10)))

        article.save()

        #redis_cli.incr("jobbole_count")

        return

ES的方法 (非必须)

def gen_suggests(index, info_tuple):
    # 根据字符串生成搜索建议数组
    used_words = set()
    suggests = []
    for text, weight in info_tuple:
        if text:
            # 调用es的analyze接口分析字符串
            words = es.indices.analyze(index=index, analyzer="ik_max_word", params={'filter': ["lowercase"]}, body=text)
            anylyzed_words = set([r["token"] for r in words["tokens"] if len(r["token"]) > 1])
            new_words = anylyzed_words - used_words
        else:
            new_words = set()

        if new_words:
            suggests.append({"input": list(new_words), "weight": weight})

    return suggests

编写Pipeline.py

导包

import pymysql
from twisted.enterprise import adbapi

mysql管道

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["MYSQL_USER"],
            passwd = settings["MYSQL_PASSWORD"],
            charset='utf8',
            cursorclass=pymysql.cursors.DictCursor,
            use_unicode=True,
        )
        dbpool = adbapi.ConnectionPool("pymysql", **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) #处理异常

    def handle_error(self, failure, item, spider):
        #处理异步插入的异常
        print (failure)

    def do_insert(self, cursor, item):
        #执行具体的插入
        #根据不同的item 构建不同的sql语句并插入到mysql中
        insert_sql, params = item.get_insert_sql()
        cursor.execute(insert_sql, params)

ES管道(非必须)

class ElasticsearchPipeline(object):
    #将数据写入到es中
    def process_item(self, item, spider):
        #将item转换为es的数据
        item.save_to_es()

        return item

编写中间件 middlwares.py

导包

from scrapy import signals
from fake_useragent import UserAgent

随机UA

class RandomUserAgentMiddlware(object):
    #随机更换user-agent
    def __init__(self, crawler):
        super(RandomUserAgentMiddlware, self).__init__()
        self.ua = UserAgent()
        self.ua_type = crawler.settings.get("RANDOM_UA_TYPE", "random")

    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler)

    def process_request(self, request, spider):
        def get_ua():
            return getattr(self.ua, self.ua_type)

        request.headers.setdefault('User-Agent', get_ua())

编写setting.py

# -*- coding: utf-8 -*-


BOT_NAME = 'LagouSpdier'

SPIDER_MODULES = ['LagouSpdier.spiders']
NEWSPIDER_MODULE = 'LagouSpdier.spiders'

ROBOTSTXT_OBEY = False


DOWNLOAD_DELAY = 60
RANDOMIZE_DOWNLOAD_DELAY = True

COOKIES_ENABLED = True

DOWNLOADER_MIDDLEWARES = {
   #'LagouSpdier.middlewares.LagouspdierDownloaderMiddleware': 543,
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 2,
}

ITEM_PIPELINES = {
    'LagouSpdier.pipelines.MysqlTwistedPipline': 2,
    'LagouSpdier.pipelines.ElasticsearchPipeline': 1,
}

import os
import sys
BASE_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
sys.path.insert(0,os.path.join(BASE_DIR,'LagouSpider'))

MYSQL_HOST = "127.0.0.1"
MYSQL_DBNAME = "article_spider"
MYSQL_USER = "root"
MYSQL_PASSWORD = "root"

SQL_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
SQL_DATE_FORMAT = "%Y-%m-%d"

编写es_type.py (非必须)

# -*- coding: utf-8 -*-

from datetime import datetime
# elasticsearch_dsl 版本 5.1.0
from elasticsearch_dsl import DocType, Date, Nested, Boolean, \
    analyzer, InnerObjectWrapper, Completion, Keyword, Text, Integer

from elasticsearch_dsl.analysis import CustomAnalyzer as _CustomAnalyzer

from elasticsearch_dsl.connections import connections
connections.create_connection(hosts=["localhost"])


class CustomAnalyzer(_CustomAnalyzer):
    def get_analysis_definition(self):
        return {}


ik_analyzer = CustomAnalyzer("ik_max_word", filter=["lowercase"])


class ArticleType(DocType):
    suggest = Completion(analyzer=ik_analyzer)

    # 拉勾网职位信息
    title = Text(analyzer="ik_max_word")
    url = Keyword()
    url_object_id = Keyword()
    salary = Keyword()
    job_city = Keyword()
    work_years = Keyword()
    degree_need = Keyword()
    job_type = Keyword()
    publish_time = Keyword()
    job_advantage = Text(analyzer="ik_max_word")
    job_desc = Text(analyzer="ik_max_word")
    job_addr = Text(analyzer="ik_max_word")
    company_name = Keyword()
    company_url = Keyword()
    tags = Text(analyzer="ik_max_word")
    crawl_time = Date()

    class Meta:
        index = "lagou"
        doc_type = "jobs"


if __name__ == "__main__":
    ArticleType.init()

运行

先debug main.py

# _*_ coding: utf-8 _*_

from scrapy.cmdline import execute

import sys
import os

print(os.path.dirname(os.path.abspath(__file__)))  # 打印目录
sys.path.append(os.path.dirname(os.path.abspath(__file__)))  # 寻找项目工程目录
execute(["scrapy", "crawl", "lagou"])