写了个招聘网站爬虫

最近觉得每天写PHP有点疲劳,想接触一点没有接触过的东西比如说Python,我之前就了解到Python很强大,能做很多事,比如说写Web,写小工具,科学计算,控制树莓派,还能写爬虫到网上爬数据。于是突发奇想,要不写一个招聘网站爬虫吧,于是就有了这么一个小项目。

我每天的工作就是写Web相关的,比如说网站,App的API接口,进销存系统等等,有更熟悉及使用范围更广的PHP,没必要舍近求远用Python来写Web。

至于为什么选择爬虫,一是因为简单,适合入门,也能从中学到Python的很多基础知识;二是因为Python有requests、pyquery这样很厉害的库,作为一个新手,对着例子改改就能用。

这个小项目使用了requests、pymysql、pyquery几个库,做个记录及给后来者参考。

出于数据量考虑,我并没有做限速处理,直接一口气跑完,也还不是很完善,总共抓去到2200+数据,220+家公司,如果完善一下应该可以抓的更多,代码示例:

import requests
import pymysql
from pymysql import MySQLError
from requests.exceptions import RequestException
from pyquery import PyQuery as pq
import time
import datetime
import re

db = pymysql.connect(host='localhost', user='zoco', password='zoco', port=3306, db='gz91', charset='utf8')
cursor = db.cursor()

# 通用头部请求
headers = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7",
    "Connection": "close",
    "Dnt": "1",
    # "Host": "httpbin.org",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36"
}

# 主域名
domain = 'http://www.gz91.com'
unix_stamp = int(time.time())
date = datetime.datetime.now().strftime("%Y%m%d")


# gz91 头部
def gz91_header():
    gz91_header_list = headers
    gz91_header_list['Host'] = 'www.gz91.com'
    return gz91_header_list


# 页面分析 抓去到页面后  进行页面分析  逐一分析是列表页  还是招聘详情页 或者是公司展示页
def page_analyze(html):
    get_a_link(html)


# 抓取首页
def get_index():
    try:
        index_html = requests.get(domain, headers=gz91_header())
        if index_html.status_code == 200:
            index_html.encoding = 'UTF-8'
            return index_html
        else:
            return None
    except RequestException:
        return None


# 判断并获得a链接
def get_a_link(html):
    html = pq(html.text)
    a_links = html('a')
    for a in a_links:
        href = pq(a).attr('href')
        href = href.lower()
        # 判断是否是展示页
        if href.find('jobsviewer') != -1:
            # 再次判断是招聘详情展示页还是公司展示页
            if href.find('jobs_id') != -1:
                # 交由页面抓取程序抓取该页面  然后交由工作页函数分析并入库
                job_page_analyze(href)
                # pass
            else:
                # 未查找到jobs_id 则为公司展示页
                company_page_analyze(href)
                # pass

        # 判断是否是搜索页
        if href.find('jobsearch') != -1:
            # print('搜索页:' + href)
            # 搜索页再次爬取  逐一爬取详情页
            # page_request(href)
            pass


# 保存 a 链接
def a_save(href):
    """
    查找是否保存传来的 href 属性,如果已经保存则更新 否则保存至数据库 a 表中
    :param href: 链接地址
    :return:
    """
    # 判断href里面是否有域名  如果有  则去掉域名信息
    if href.find('www.gz91.com') > -1:
        href = href.replace('http://www.gz91.com', '')

    a_href_query_sql = "SELECT COUNT(1) as count FROM a WHERE href = %s"
    cursor.execute(a_href_query_sql, href)
    href_query = cursor.fetchone()

    if href_query[0] == 0:
        # 执行入库步骤
        try:
            href_insert_sql = "insert into a (href, last_visit, last_visit_date) VALUES (%s, %s, %s)"
            cursor.execute(href_insert_sql, (href, unix_stamp, date))
            db.commit()
        except MySQLError as e:
            db.rollback()
            print('a链接保存失败,错误信息:'.format(e, e.args[0]))


# 抓取网页
def page_request(href):
    # 判断传来的链接是否带域名  如果携带则不用添加域名信息  否则需要加上域名信息
    if href.find('www.gz91.com') > -1:
        url = href
    else:
        # 判断URL里面是否自带/   如果带了  就没必要传/进URL
        if href.find('/') == 0:
            url = domain + href
        else:
            url = domain + '/' + href
    # 保存a链接
    # print(url)
    # return
    a_save(url)
    try:
        page = requests.get(url=url, headers=gz91_header())
        if page.status_code == 200:
            page.encoding = 'UTF-8'
            # print(page.text)
            # exit(1)
            return page
        return None
    except RequestException:
        return None


# 分析并保存招聘页信息
def job_page_analyze(href):
    print(href)
    ids = re.search('.*?/jobsviewer/index/job7733_id/(.*?)/jobs_id/(.*?).html', href)
    # print(ids.groups())
    if len(ids.groups()) == 2:
        # print(job_id_list[2])
        jobs_id = ids.groups()[1]
        company_id = ids.groups()[0]
    else:
        return
    # print(jobs_id,company_id)
    # 从数据库中查找  是否已存在这条职位信息  如果已存在 则不再进行写入
    job_query_sql = "SELECT count(1) as count FROM jobs WHERE jobs_id = %s AND company_id = %s"
    cursor.execute(job_query_sql, (jobs_id, company_id))
    job_query = cursor.fetchone()
    # print(job_query)
    if job_query[0] > 0:
        return

    page = page_request(href)
    # 开始从网页中找到职位相关信息
    html = pq(page.text)
    # 职位名称
    job_name = html.find('.zw_a #jtname').text()
    # print(job_name)
    # 职位月薪
    monthly_salary = html.find('.zw_if1 dd:first em').text()
    work_place = html.find('.zw_if1 dd:nth-child(2) i').text()
    position_type = html.find('.zw_if1 dd:nth-child(3) i').text()
    sex_requirements = html.find('.zw_if1 dd:nth-child(4) i').text()
    number_of_recruits = html.find('.zw_if1 dd:nth-child(5) i').text()
    job_category = html.find('.zw_if1 dd:nth-child(6) i').text()
    age_requirement = html.find('.zw_if1 dd:nth-child(7) i').text()
    work_experience = html.find('.zw_if1 dd:nth-child(8) i').text()
    update_at = html.find('.zw_if1 dd:nth-child(9) i').text()
    education_requirements = html.find('.zw_if1 dd:nth-child(10) i').text()
    driver_license = html.find('.zw_if1 dd:nth-child(11) i').text()
    certificate_requirement = html.find('.zw_if1 dd:nth-child(12) i').text()
    # 工作时间
    working_hours = html.find('.wz_if3 dd:nth-child(1) i').text()
    # 工作详情
    job_detail = html.find('.wz_if4:first dd').text()
    # print(job_detail)
    # 插入数据库
    jobs_insert_sql = "INSERT INTO `gz91`.`jobs`(`jobs_id`, `job_name`, `monthly_salary`, `work_place`, " \
                      "`position_type`, `sex_requirements`, `number_of_recruits`, `job_category`, `age_requirement`, " \
                      "`work_experience`, `update_at`, `education_requirements`, `driver_license`, `job_detail`, " \
                      "`certificate_requirement`, `working_hours`, `company_id`) VALUES " \
                      "(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);"
    insert_set = (jobs_id, job_name, monthly_salary, work_place, position_type, sex_requirements, number_of_recruits,
                  job_category, age_requirement, work_experience, update_at, education_requirements, driver_license,
                  job_detail, certificate_requirement, working_hours, company_id)
    try:
        cursor.execute(jobs_insert_sql, insert_set)
        db.commit()
        print('职位ID为 ' + jobs_id + '的工作信息入库完成')
    except MySQLError as e:
        db.rollback()
        # print('页面数据保存失败,错误信息:'+format(e, e.args[0]))
        print(e.args)


# 分析并保存公司信息
def company_page_analyze(href):
    """
    根据传来的信息到数据库查询是否有该公司的信息  如果没有  则进行保存   有的话进行更新
    :param page: 抓取到的页面
    :param href: 公司地址
    :return:
    """
    # print(page.text)
    print(href)
    # 从URL中  分析出公司ID
    company_id = re.search('.*?/jobsviewer/index/job.*?_id/(.*?).html', href).group(1)
    # print(company_id)

    # 查询当前公司是否在数据库   如果不在则入库  在直接返回
    company_query_sql = "SELECT count(1) as count FROM company WHERE company_id= %s"
    cursor.execute(company_query_sql, company_id)
    company_query = cursor.fetchone()
    # print(job_query)
    if company_query[0] > 0:
        return

    page = page_request(href)
    # 从页面中找出各项需要插入数据库的信息
    html = pq(page.text)
    company_name = html.find('.cominfo dt h1').text()
    address = html.find('.com_lx dt em a').text()
    industry = html.find('.cominfo dd.posr span.hy').text()
    industry = industry.replace('行业:', '')
    scale = html.find('.cominfo dd.posr span.gm').text()
    scale = scale.replace('规模:', '')
    url = company_id
    nature = html.find('.com_if1 dd:first i').text()
    date_of_establishment = html.find('.com_if1 dd:nth-child(2) i').text()
    industry_category = html.find('.com_if1 dd:nth-child(3) i').text()
    numbers_of_employees = html.find('.com_if1 dd:nth-child(4) i').text()
    telphone_number = html.find('.com_lx dt:first em').text()
    introduction = html.find('.wz_if4 dd:nth-child(2)').text()
    last_modifiy = date
    # 插入数据库
    company_inset_sql = 'INSERT INTO `gz91`.`company`(`company_id`, `company_name`, `address`, `industry`, ' \
                        '`scale`, `url`, `nature`, `date_of_establishment`, `industry_category`, `numbers_of_employees`,' \
                        ' `telphone_number`, `introduction`, `last_modifiy`) ' \
                        'VALUES ( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);'
    insert_set = (company_id, company_name, address, industry, scale, url, nature, date_of_establishment,
                  industry_category, numbers_of_employees, telphone_number, introduction, last_modifiy)
    try:
        cursor.execute(company_inset_sql, insert_set)
        db.commit()
        print('ID为 ' + company_id + '的公司信息入库完成')
    except MySQLError as e:
        db.rollback()
        # print('页面数据保存失败,错误信息:'+format(e, e.args[0]))
        print(e.args)


lmjobs_uri = '/recommend/lmjobs.html'


# 冷门职位
def lmjobs(lmjobs_next_page_a):
    lmjobs_index = requests.get(url=domain + lmjobs_next_page_a, headers=gz91_header())
    if lmjobs_index.status_code == 200:
        page_analyze(lmjobs_index)
        # 从页面获取下一页的URL  如果获取成功  则进行下一页抓取
        html = pq(lmjobs_index.text)
        lmjobs_next_page_a = html.find(".pagination li a.nextpage_a").attr('href')
        print(lmjobs_next_page_a)
        if lmjobs_next_page_a is not None:
            lmjobs(lmjobs_next_page_a)


def main():
    index_html = get_index()
    # print(index_html.text)
    page_analyze(index_html)
    lmjobs(lmjobs_uri)


if __name__ == '__main__':
    main()

对应的数据库结构:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for a
-- ----------------------------
DROP TABLE IF EXISTS `a`;
CREATE TABLE `a` (
  `a_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `href` varchar(255) NOT NULL COMMENT '链接值',
  `last_visit` varchar(12) NOT NULL COMMENT '最后访问时间',
  `last_visit_date` int(8) NOT NULL COMMENT '最后访问时间-日期',
  PRIMARY KEY (`a_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4981 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for company
-- ----------------------------
DROP TABLE IF EXISTS `company`;
CREATE TABLE `company` (
  `c_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '公司表 ID',
  `company_id` int(11) NOT NULL COMMENT '公司在 gz91的 ID',
  `company_name` varchar(255) NOT NULL COMMENT '公司名称',
  `address` varchar(255) DEFAULT NULL COMMENT '公司地址',
  `industry` varchar(255) DEFAULT NULL COMMENT '所属行业',
  `scale` varchar(255) DEFAULT NULL COMMENT '公司规模',
  `url` varchar(255) DEFAULT NULL COMMENT '公司网站 URL',
  `nature` varchar(255) DEFAULT NULL COMMENT '公司性质',
  `date_of_establishment` varchar(10) DEFAULT NULL COMMENT '公司成立日期',
  `industry_category` varchar(255) DEFAULT NULL COMMENT '行业类别',
  `numbers_of_employees` varchar(20) DEFAULT NULL COMMENT '员工人数',
  `telphone_number` varchar(15) DEFAULT NULL COMMENT '联系电话',
  `introduction` text COMMENT '公司简介',
  `last_modifiy` int(11) DEFAULT NULL COMMENT '最后修改时间',
  PRIMARY KEY (`c_id`)
) ENGINE=InnoDB AUTO_INCREMENT=231 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for jobs
-- ----------------------------
DROP TABLE IF EXISTS `jobs`;
CREATE TABLE `jobs` (
  `j_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '工作 ID',
  `jobs_id` int(11) NOT NULL COMMENT '在gz91所属工作 ID',
  `job_name` varchar(255) NOT NULL COMMENT '职位名称',
  `monthly_salary` varchar(15) NOT NULL DEFAULT '' COMMENT '提供月薪',
  `work_place` varchar(20) DEFAULT NULL COMMENT '工作地点',
  `position_type` varchar(20) DEFAULT NULL COMMENT '职位类型',
  `sex_requirements` varchar(10) DEFAULT NULL COMMENT '性别要求',
  `number_of_recruits` varchar(10) DEFAULT NULL COMMENT '招聘人数',
  `job_category` varchar(20) DEFAULT NULL COMMENT '岗位类别',
  `age_requirement` varchar(255) DEFAULT NULL COMMENT '年龄要求',
  `work_experience` varchar(20) DEFAULT NULL COMMENT '工作经验',
  `update_at` varchar(20) DEFAULT NULL COMMENT '最后更新',
  `education_requirements` varchar(20) DEFAULT NULL COMMENT '学历要求',
  `driver_license` varchar(10) DEFAULT NULL COMMENT '是否要求驾驶证',
  `job_detail` text COMMENT '职位介绍详情',
  `certificate_requirement` varchar(10) DEFAULT NULL COMMENT '证书要求',
  `working_hours` varchar(100) DEFAULT NULL COMMENT '工作时间',
  `company_id` int(11) NOT NULL COMMENT '公司ID',
  PRIMARY KEY (`j_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1891 DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;