上面介绍的spider篇主要为网页的抓取和数据在容器的储存
接下来介绍管道篇,涉及数据的初步处理,如去重,清洗和把数据存入mysql数据库
首先思考要导入的模块
将数据存入mysql需要pymysql模块,pymysql模块是用python操作数据库的模块
在去重的时候可能需要DropItem模块,在数据重复的时候直接抛异常
再要思考数据处理的顺序
是先去重还是先清洗?根据逻辑判断去重后再清洗会使爬虫的效率变高,因为如果先清洗后去重会导致清洗到根本不需要清洗的数据,导致爬虫的效率变低,
所以正确的顺序是先去重再清洗最后再储存
下面是数据去重的思考
首先为什么要数据去重?什么要的数据是重复的?
仔细观察,职位名显然不能成为判断数据是否重复的标准,因为很多职位名是重复的。
公司名也不能称为判断数据是否重复,出现两个或者以上一样的公司名并不代表他们发布的职位名是一样的。
综上,只有公司名和职位名结合在一起才是判断数据是否重复的唯一标准。
有了衡量数据的标准后,后面就是思考怎么去重
一般的去重方法就是先创建个容器(一般为数组)储存数据,再查找数据是否在容器中,如果在,直接抛出异常,如果不在将其添加到容器中,并return item。
贴出去重的代码块
class DuplicatePipeline(object):
def __init__(self):
self.name_seen=set()
def process_item(self,item,spider):
if item['company']+item['job'] in self.name_seen:
# raise:直接抛出异常
# DropItem():异常函数参数可以加入任意提示语句
raise DropItem('Duplicate item found %s' %item)
else:
self.name_seen.add(item['company']+item['job'])
return item
之后就是数据清洗过程的分析了
分析数据 首先是工作地点,并不需要后面的区名,区名太多会影响数据分析的负担
其次是工资,而51job的工资数据都是用”-“分割的区间数据,所以需要切割,制定标准,工资的标准有万/年,万/月,千/月,元/天的,我们统一把工资的标准定为千/月。工资的标准很关键,因为后续的数据分析过程中会计算均值,中位数等统计数据,
最后是时间 我们需要将时间前面加上2018
在数据清洗的过程我们需要用到两个函数(在数据处理很普遍的函数)
strip 去除数据前后空格
split 分割数据
下面贴出代码块
#1.数据清洗
class DataCleanPipline(object):
def process_item(self,item,spider):
print('+-'*100)
#(1)去除职位名称的数据前后空白
item['zw']=item['zw'].strip()
#(2)分割职位月薪数据,保存成最大薪资,最小薪资
if item['xz']!='':
xz_list=item['xz'].strip().split('-')
#判断是否切割成两部分
if len(xz_list)>1:
#判断薪资是否是 万/月 的数据
if xz_list[1][-1]=='月' and xz_list[1][-3]=='万':
item['xz_lowest']=float(xz_list[0])*10000
item['xz_highest']=float(xz_list[1][0:-3])*10000
# 判断薪资是否是 千/月 的数据
elif xz_list[1][-1]=='月' and xz_list[1][-3]=='千':
item['xz_lowest']=float(xz_list[0])*1000
item['xz_highest']=float(xz_list[1][0:-3])*1000
# 判断薪资是否是 万/年 的数据
elif xz_list[1][-1]=='年' and xz_list[1][-3]=='万':
item['xz_lowest']=round((float(xz_list[0])*10000)/12,1)
item['xz_highest']=round((float(xz_list[1][0:-3])*10000)/12,1)
else: #其他情况,传0
item['xz_lowest'] = item['xz_highest'] = 0
else: #处理是切割后只有一个元素( 元/天的数据)
item['xz_lowest'] = item['xz_highest']=float(xz_list[0][0:-3])*22
else: #如果薪资是空字符串,传入0
item['xz_lowest'] = item['xz_highest'] = 0
#(3)公司地点信息的切割,保留城市名,切掉区名
item['dd']=item['dd'].split('-')[0]
#(4)日期格式转换
item['rt']='2018-'+item['rt']
return item
最后就是数据储存过程
这是我爬取的数据,总共37000多条,可以发现数据之多,所以我在mysql中创建了四张表,分别储存职位名,公司名和地点名。创建四张表固然会使代码量增加和代码复杂程度增加,但四张表分别储存数据会使数据更加简洁明确化,在数据查询中也会更加有效率,我建议大家在mysql储存数据中分表储存是一个比较好的方法
分表储存的关键在于将表的编号传递给总表
以公司名称为例子
我们需要将ID传递给总表,如何做到这一点呢?
我的解决方法是先查询该表中是否有容器中的公司名,如果有的话,将ID用一个对象储存,如果没有的话再将公司名称插入公司表,再查询id号,储存在对象中。
在储存过程中要熟悉pymysql的用法
pymysql的基本步骤为:1.创建连接,一般要输入mysql的host,user,passwd,db,charset(与普通的charset不同的是utf8 不能加”-“)
2.创建游标 cursor
3.sql语句与parm(主要是数据)
4.execute 和commit
5.关闭cursor和connect
pymysql的练习代码块如下‘
import pymysql
# 连接数据库
conn=pymysql.connect(host='localhost',user='root',passwd='123456',db='myschool',charset='utf8')
# 创建游标
cursor=conn.cursor()#(命令行,操作行都是通过游标来执行的)
# sql语句
# sql='insert into grade(gid,gradeName) values(%s,%s)'
# parm=('1001','大一')
# 插入多条
# sql='insert into grade(gradeName) values(%s)'
# parm=('大二','大四')
# update
# sql='update grade set gradeName=%s where gid=%s'
# parm=('研一','1002')
# 多条更新
# sql='update grade set gradeName=%s where gid=%s'
# parm=(('研一','1002'),('研二','1003'))
# 删除
# sql='delete from grade where gid=%s'
# parm='1005'
# 执行sql语句
# 多条
# cursor.executemany(sql,parm)
#一条
# cursor.execute(sql,parm)
# 提交
conn.commit()
# 查询
sql='select * from grade'
cursor.execute(sql)
# 打印查询结果
# 打印一条
# print(cursor.fetchone())
# 打印多条
# print(cursor.fetchmany(2))
# 打印所有
# print(cursor.fetchall())
# 关闭
cursor.close()
conn.close()
储存过程代码块如下
#数据存储
class load_dataPipeline(object):
#连接数据库
def open_spider(self, spider):
self.conn = pymysql.connect(host='localhost', user='root', passwd='123456',
db='saler', charset='utf8')
def process_item(self,item,spider):
cursor=self.conn.cursor()
#dd表
cursor.execute('select * from ca_dd where dd_name=%s',item['place'])
a=cursor.fetchone()
if a:
total_dd=a[0]
else:
cursor.execute('insert into ca_dd values (null,%s)',item['place'])
cursor.execute('select * from ca_dd where dd_name=%s',item['place'])
b=cursor.fetchone()
total_dd=b[0]
#公司名称表
cursor.execute('select * from ca_gsmc where gsmc_name=%s', item['company'])
gsmc=cursor.fetchone()
if gsmc:
total_gsmc=gsmc[0]
else:
cursor.execute('insert into ca_gsmc values (null,%s)', item['company'])
cursor.execute('select * from ca_gsmc where gsmc_name=%s', item['company'])
gsmc=cursor.fetchone()
total_gsmc=gsmc[0]
#职位名称表
cursor.execute('select * from ca_zwmc where zwmc_name=%s', item['job'])
zwmc=cursor.fetchone()
if zwmc:
total_zwmc =zwmc[0]
else:
cursor.execute('insert into ca_zwmc values (null,%s)', item['job'])
cursor.execute('select * from ca_zwmc where zwmc_name=%s', item['job'])
zwmc= cursor.fetchone()
total_zwmc=zwmc[0]
#插入总表
sql='insert into ca_list values(null,%s,%s,%s,%s,%s,%s,%s)'
parm=(total_zwmc,total_gsmc,total_dd,item['lowest_salary'],item['highest_salary'],item['time'],item['href'])
cursor.execute(sql,parm)
self.conn.commit()
cursor.close()
# 爬虫结束后,断开数据库连接
def close_spider(self, spider):
self.conn.close()
最后一部分也很关键,需要将管道开启
我们进入settings,介绍一个功能键ctrl+F 在很多地方都能使用,是一个搜索和替换内容的快捷键
用快捷键搜索PIpelines 按照上诉格式修改settings中的代码,格式是 ‘项目名.pipelines.管道名’:数字
数字越小代表优先级越高,我们通过修改数字的大小来设置管道的执行顺序。