最近觉得每天写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;