爬虫之刃----赶集网招聘类爬取案例详解(系列四)

前言


本篇承袭之前的系列文章,开始动真格。以赶集网招聘类信息爬取为例,详细解说爬虫程序构建过程。

准备工作:

  1. 阅读之前的系列一、系列二、系列三,有一定递进关系
  2. 登陆赶集网,了解下“地形”

OK,let’s go!


构建URL库


每个网站的URL都会有一定规律,或强或弱。赶集网的URL规律就非常明显。

郑重说明:对于目标站点,必须熟悉。这一步对于不同的网站有不同的方法。而赶集的话,是我通过仔细观察得到url规律。以赶集招聘类目举例,如下:

  1. http://www.ganji.com/index.htm 页面包含了所有的城市,每个城市有固定的简称,如:上海是sh,北京是bj
  2. 每个城市的招聘类目网址均带有zhaoping,如:上海是http://sh.ganji.com/zhaopin/,北京是http://bj.ganji.com/zhaopin/
  3. 每个城市的职业分类均是相同的,具体的职业分类标识均是相同的,如:电话销售是zpdianhuaxiaoshou,售前工程师是zpsqgongchengshi。相应的该类目下的链接是(已北京为例):http://bj.ganji.com/zpdianhuaxiaoshou/,http://bj.ganji.com/zpsqgongchengshi/
  4. 有了city+category的URL以后,可以在URL后追加页数,如北京的电话销售第一页:http://bj.ganji.com/zpdianhuaxiaoshou/o1/ ,第二页:http://bj.ganji.com/zpdianhuaxiaoshou/o2/

URL规律就说到这里。顺便一提,赶集网的3G手机版的也是相似的规律。

说说几个误区:

  1. 担心漏爬。因为分类信息网站的分类还有很多,刚才我分析的太简单了?
    爬虫之刃----赶集网招聘类爬取案例详解(系列四)_第1张图片
    负责的告诉大家,以上的分类完全不用管,不必去构造区域(海淀、朝阳、东城等)、月薪、福利、其他的URL规律。例如只要选了电话销售,这些信息均包含在此链接下。更多的筛选条件只是决定了哪些信息可以优先显示罢了,对于全部需要(如果你不需要全部,当我没说)爬取的爬虫是没有意义的。

  2. 所有的数据都应该遵循自动获取(除了第一个url是手动输入),而非人工录入。比如,城市类目。虽然,城市类目一般不会变,但是如果赶集增加和放弃某个/些城市了,爬虫就完了。

  3. 没有固定的和绝对的规则/规律,一定要保持爬虫的灵活。不要以为总结了很多赶集的规律,就算结束了。实时盯着自己的爬虫。比如,城市类目里:
    爬虫之刃----赶集网招聘类爬取案例详解(系列四)_第2张图片
    最后一行,居然有钓鱼岛!我只能告诉你,这个城市链接下的数据和其他所有城市都不一样。你的爬虫里的正则、xpath、爬取方式在钓鱼岛下均不适用。但是,你一定要知道:钓鱼岛是中国的!


去重模块


去重去的好,可以节省很多的时间。但是在不同的应用场景里是有很多差异之处。切记套模板,必须自己去思考。

在赶集网的爬取过程中,因为需求是:爬到更多的新数据。而这样必然产生大量的数据。所以,我没有采用每个具体页面的id去重(去重速度慢。一般的应用场景应当考虑利用id),而是判断listing页面(一般包含30个具体页面的摘要信息)是否已爬过。

所以,构建了一个去重的数据库。表结构:
谷震平 blog

把每次爬到的URL都放在mongo里,设置如下几个字段:

  • _id:等同于cityCategoryPageUrl字段
  • cityCategoryPageUrl: 城市+类目+页数的URL
  • cityCategoryUrl: 城市+类目的URL
  • status: 该cityCategoryPageUrl是否已爬过。0:未爬过,1:已爬
  • endPage: 该cityCategoryUrl是否已爬完。0:为爬完,1:已爬
  • crawlTime:插入数据库的时间
  • crawlDay: 插入数据库的日期

OK,写到这里,声明下:我是按cityCategoryPageUrl这个字段的值来爬数据的。这个字段的状态是0就去爬取该页面,否则放弃。

那endPage是干什么用的?
因为每次中断后,就不知道该是从那个页面开始了。不想每个页面都检查一遍是否是已爬过(status == 1)状态。所以先检查cityCategoryUrl是否是已爬过。

梳理一下:
爬虫之刃----赶集网招聘类爬取案例详解(系列四)_第3张图片

最后,再次强调:去重模块的数据表中,没有具体帖子的URL状态。因为,数据太多了,每次都检查不过来。而且帖子都在新增和删除(用户行为),是变动的数据。


爬取模块


既然URL已经构建好了,爬取就变得简单了。利用requests模块库发送HTTP请求,得到网站源码,利用lxml模块库进行DOM化,结合xpath爬取即可。中间的个别技术问题,一样网络均有答案,不再赘述。

给出一份Python版伪代码,仅供参考:

def proxy():
	"""购买代理IP"""
	proxies = 购买代理IP
	return proxies

def check_fanpa(response):
	"""检测反爬虫页面"""
	title = 检测网页内容,如判断title内容
	if "404" in title:
		check_result = False
	elif "502" in title:
		check_result = False
	elif "反爬取" in title:
		check_result = False
	else:
		check_result = True
	return check_result
	
def request(url):
	"""发送HTTP请求,获得源码"""
	hea={常用的http头}
	proxies = proxy()   # 调用,获取代理IP
	response = requests.get(url,proxies=proxies,headers=hea,timeout=1)
	
	check_fanpa_result = check_fanpa(response)    # 调用,获取反爬虫检测结果
	if check_fanpa_result is False:
		再次尝试http请求,这里可以有一个请求限制,最多/10else:
		return response    # 返回正常结果
	return None  # 超出了请求限制的特殊情况

def check_quchong(url):
	"""url去重"""
	quchong_result = 在已爬过的数据中寻找是否有相同的url
	if quchong_result is not None:
		return False   # 重复的URL
	else:
		return True    # 没有该URL
		
def parse(response):
	xpath_map = {
		"title_xpath": "//html/header/title/text()" ,
		"...": "...",
		"...": "...",
	}
	tree = lxml.etree.HTML(response.text)
	title_data = tree.xpath(xpath_map['title_xpath'])
	
	data = {}    
	
	data['title'] = title_data[0]
	
	return data    # 返回所用数据

def storage(data):
	"""数据存储,细节过程就不说了"""
	将data放在mongo中
	return True

def crawl():
	"""爬取模块"""
	url = 从构建的url库中获取
	check_quchong_result = check_quchong(url)   # 调用,检测是否已经爬过
	if check_quchong_result is True:
		return None 
	else:
		response = request(url)    # 调用,获得源码/None
		if response is None:
			return None  # 因为是异常,返回None,当然也可用pass让程序忽略继续向下走
		
		parse_data = parse(response)  # 调用,获得解析好的data
		storage_res = storage(parse_data)  # 调用,存储数据

实际上,以上代码只是展示了基本的爬虫逻辑。实际应用中,完全不能这么写。上面的伪代码连最基本的异步都没有做的,所以就看看逻辑就好。一般的爬虫,和上面的爬取逻辑并无二致。


代理模块


在上述爬取过程中,大量快速的访问,将触犯赶集的反爬取机制,HTTP请求后的response都是不包含可用信息的源码。这些页面的种类有很多,如:反爬页面,404页面,403页面,502页面等等。我在自己的github上共享过相关的资料,仅供参考,请戳传送门----赶集反爬。后期,会有一篇文章展开讲反爬的故事。

面对众多的不可用页面的情况,最好的办法就是使用代理IP。在使用代理的过程中,也必须验证返回的源码是否是自己真正需要的,不是,就必须换一个代理IP,重新去请求。有些场景,需要不断换代理IP,直到获得真正的页面源码;有些场景,设置请求次数,如10次,换10个不同的代理IP之后,如果拿到的还不是自己需要的源码,就放弃该页面。


存储模块


要存的数据大致有两种:

  • 临时存储的数据
  • 永久存储的数据

临时存储的数据,放在redis中;永久存储的数据,放在mongo中。根据应用场景,灵活的配合使用。

redis可以存放http请求后的源码。将网页源码序列化后,存放在redis队列中,再利用其它进程,如解析进程,进行解析。得到的数据如果是永久数据,可以直接存放到mongo中。如果还有需求方需要,可以再次放在redis中,让该需求方去拿即可。

构建异步程序过程中,使用redis有两种基本的方法:

  • 生产者–消费者模式
  • 发布者–订阅者模式

两者区别较大,设计redis队列需要考虑。相关资料参考博文:【Redis----异步程序之消息队列的应用与实践】。

mongo用于存放永久数据,比较简单方便。保证插入时不出错,是爬虫程序中最常见的需求。可以在插入数据出错后,检测一下是否有相同的key。视情况,放弃该数据,或者更新该数据。


结语


写的越多,发现想说的越多。其中细节,怎么说也说不完。所以,任何问题,欢迎留言交流。_

文章同步更新在简书、稀土掘金、知乎、CSDN博客、微信公众号上,个人原创,尊重版权,转载请声明!

更多精彩内容,可以关注微信公众账号【谷震平的专栏】获取。
爬虫之刃----赶集网招聘类爬取案例详解(系列四)_第4张图片
欢迎加入星球~

你可能感兴趣的:(爬虫系列)