本篇承袭之前的系列文章,开始动真格。以赶集网招聘类信息爬取为例,详细解说爬虫程序构建过程。
准备工作:
OK,let’s go!
每个网站的URL都会有一定规律,或强或弱。赶集网的URL规律就非常明显。
郑重说明:对于目标站点,必须熟悉。这一步对于不同的网站有不同的方法。而赶集的话,是我通过仔细观察得到url规律。以赶集招聘类目举例,如下:
URL规律就说到这里。顺便一提,赶集网的3G手机版的也是相似的规律。
说说几个误区:
担心漏爬。因为分类信息网站的分类还有很多,刚才我分析的太简单了?
负责的告诉大家,以上的分类完全不用管,不必去构造区域(海淀、朝阳、东城等)、月薪、福利、其他的URL规律。例如只要选了电话销售,这些信息均包含在此链接下。更多的筛选条件只是决定了哪些信息可以优先显示罢了,对于全部需要(如果你不需要全部,当我没说)爬取的爬虫是没有意义的。
所有的数据都应该遵循自动获取(除了第一个url是手动输入),而非人工录入。比如,城市类目。虽然,城市类目一般不会变,但是如果赶集增加和放弃某个/些城市了,爬虫就完了。
没有固定的和绝对的规则/规律,一定要保持爬虫的灵活。不要以为总结了很多赶集的规律,就算结束了。实时盯着自己的爬虫。比如,城市类目里:
最后一行,居然有钓鱼岛!我只能告诉你,这个城市链接下的数据和其他所有城市都不一样。你的爬虫里的正则、xpath、爬取方式在钓鱼岛下均不适用。但是,你一定要知道:钓鱼岛是中国的!
去重去的好,可以节省很多的时间。但是在不同的应用场景里是有很多差异之处。切记套模板,必须自己去思考。
在赶集网的爬取过程中,因为需求是:爬到更多的新数据。而这样必然产生大量的数据。所以,我没有采用每个具体页面的id去重(去重速度慢。一般的应用场景应当考虑利用id),而是判断listing页面(一般包含30个具体页面的摘要信息)是否已爬过。
把每次爬到的URL都放在mongo里,设置如下几个字段:
OK,写到这里,声明下:我是按cityCategoryPageUrl这个字段的值来爬数据的。这个字段的状态是0就去爬取该页面,否则放弃。
那endPage是干什么用的?
因为每次中断后,就不知道该是从那个页面开始了。不想每个页面都检查一遍是否是已爬过(status == 1)状态。所以先检查cityCategoryUrl是否是已爬过。
最后,再次强调:去重模块的数据表中,没有具体帖子的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请求,这里可以有一个请求限制,最多/少10次
else:
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博客、微信公众号上,个人原创,尊重版权,转载请声明!