一、爬虫入门知识

成功的秘诀,在永不改变既定的目的。 [ —卢梭 ]


特别声明:这个系列的爬虫相关知识总结,是根据小象学院(分布式爬虫课程)梳理而来。一方面作为自己学习过程的笔记,另一方面给想入门的同学提供参考。


本篇分为以下几部分:

  • 环境搭建
  • HTTP协议
  • 宽度与深度抓取
  • 不重复抓取策略
  • 网站结构分析
  • 代码示例

环境搭建

该部分比较简单(略)。
网上已经有很多教程了,可以手动安装,用pip安装各种依赖包,也可以安装类似于Anaconda 的集成环境。需要指出的是该系列课程是基于python2的。

Http协议

层协议

一、爬虫入门知识_第1张图片

  • 物理层:电气连接
  • 数据链路层:交换机、STP、帧中继
  • 网络层:路由器、IP协议
  • 传输层:TCP/UDP协议
  • 会话层:建立通信连接、网络拨号
  • 表示层:每次连接只处理一个请求
  • 应用层:HTTP/FTP
Keepalive

http是一个请求<->响应模式的典型范例。即客户端向服务器发送一个请求信息,服务器来响应这个信息。在老版本的http中,每个请求都将被创建一个新的连接,在此基础上发送接收请求,其优点是简单、易于理解与实现。但效率很低。

keep-alive功能使得客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,可避免建立重新连接。
默认情况下,http1.1中的所有连接都被保持,除非在请求头或响应头中指明关闭:Connection:Close

Http请求方法

一、爬虫入门知识_第2张图片

宽度与深度抓取

网页抓取原理

一、爬虫入门知识_第3张图片
首先从根节点开始抓取,分析根节点超链接放入队列(要求无重复),依次访问队列中的链接,根据策略不同可分为广度优先与深度优先。

深度优先策略

一、爬虫入门知识_第4张图片

从A到D依次抓取,然后回到C,横向抓取到G,再回到B再横向抓取。

广度优先策略

一、爬虫入门知识_第5张图片

抓取策略选择

  • 重要的网页往往距离种子站点比较近
  • 万维网的深度并没有很深,一个网页有很多路径可以到达
  • 宽度优先有利于多爬虫并行合作抓取
  • 深度限制与宽度优先相结合

不重复抓取策略

记录抓取历史的方法

  • 将访问过的URL保存到数据库(效率太低)
  • 用HashSet将访问过的URL保存起来。只需接近O(1)的代价就可以查到一个URL是否被访问过了(消耗内存)
  • URL经过MD5或SHA-1等单向哈希后再保存到HashSet或数据库
  • Bit-Map方法。建立一个BitSet,将每个URL经过一个哈希函数映射到某一位

    实际操作中需要:

  • 评估网站的网页数量(如site:www.taobao.com)

  • 选择合适的HASH算法和空间阈值,降低碰撞几率
  • 选择合适的存储结构和算法

可遵循的原则:

  • 多数情况下不需要压缩,尤其网页数量少的情况
  • 网页量大的情况下,使用Bloom Filter压缩
  • 重点是计算碰撞概率,并根据碰撞概率来确定存储空间的阈值
  • 分布式系统,将散列映射到多台主机的内存

网站结构分析

  • 网站对爬虫的限制
  • 利用sitemap分析网站结构和估算目标网页的规模
  • 直接对目标网页.html进行抓取
    一、爬虫入门知识_第6张图片

代码示例

该示例抓取马蜂窝网站上的各个城市游记数据(该网站结构清晰,比较典型)。

初始声明

import urllib2
import httplib
import re
from pybloomfilter import BloomFilter
import os
request_headers = {
    'host': "www.mafengwo.cn",
    'connection': "keep-alive",
    'cache-control': "no-cache",
    'upgrade-insecure-requests': "1",
    'user-agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36",
    'accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    'accept-language': "zh-CN,en-US;q=0.8,en;q=0.6"
}
#定义保存游记的目录
dirname = 'mafengwo_city_notes/'
#城市代码
city_code = []

确定如何记录已抓取的网页

这里采用pybloomfilter

download_bf = BloomFilter(1024*1024*16,0.01)
#实际大小需具体分析预估所抓取网页数量

如何下载页面游记

打开www.mafengwo.com会发现所有的游记都是http://www.mafengwo.cn/yj/i/1-0-j.html的格式i是城市代码j是页面序号。因此抓取的思路就是先遍历城市代码,在每个城市下遍历游记页码,在该页码下抓取游记,游记页面格式为www.mafengwo.cn/i/后面跟7位数字。如http://www.mafengwo.cn/i/6536459.html
下载页面游记代码如下:

def download_city_notes(citycode):
#页面最大值设为999,应该没那么多页,但应大于最大页码,下面会有退出条件
    for i in range(1,999):
        url = "http://www.mafengwo.cn/yj/%s/1-0-%d.html" % (citycode,i)
        #判断是否已经抓取,如果是开始下一页
        if url in download_bf:
            continue
            pass
        print 'start open url %s' % (url)
        #即将抓取的放进download_bf
        download_bf.add(url)
        #发起请求
        req_yj = urllib2.Request(url,headers= request_headers)
        #接收响应
        response_yj = urllib2.urlopen(req_yj)
        #读取内容
        htmlcontent_yj = response_yj.read()
        #按游记格式匹配出html
        city_notes_url = re.findall('href="/i/\d{7}.html',htmlcontent_yj)
        # 这里就要判断该页面下游记是否抓完,抓完就返回,进入下一个城市
        if len(city_notes_url)  == 0:
            return
        #下面开始处理每篇游记,因为city_notes_url是一个页面下的所有游记
        for city_note_url in city_notes_url:
            try:
        #游记url
                notes_url = 'http://www.mafengwo.cn%s'%(city_note_url[6:])
        #为避免重复抓取,依然要判断是否已抓
                if notes_url in download_bf:
                    continue
                    pass
                print 'download %s'%(notes_url)
                req_note = urllib2.Request(notes_url,headers=request_headers)
                response_note = urllib2.urlopen(req_note)

                htmlcontent_note = response_note.read()
                #定义游记名
                filename = city_note_url[7:].replace('/','_')
                #打开文件,写入
                fo = open("%s%s"%(dirname,filename), 'wb+')
                fo.write(htmlcontent_note)
                #及时关闭
                fo.close()
                download_bf.add(notes_url)
            except Exception,Arguments:
                print Arguments
                continue

获取城市代码并迭代抓取

因为拿到城市代码,就可以遍历该城市下的游记了,执行入口在此:

if __name__ == '__main__':
#判断目录是否存在,不存在则创建
    if not os.path.exists(dirname):
        os.makedirs(dirname)
        pass
    try:
 #获取城市代码,在目的地页面下,格式为“www.mafengwo.cn/mdd/”
        req = urllib2.Request("http://www.mafengwo.cn/mdd/",headers=request_headers)
        response = urllib2.urlopen(req)
        htmlcontent = response.read()
 #城市主页
        city_home_pages = re.findall('/travel-scenic-spot/mafengwo/\d{5}.html',htmlcontent)
        for city in city_home_pages:
 #截取城市代码
            city_code.append(city[29:34])
 #开始下载该城市下的游记了
            download_city_notes(city[29:34])

    except urllib2.HTTPError,Arguments:
        print Arguments
    except httplib.BadStatusLine:
        print 'BadStatusLine'
    except Exception,Arguments:
        print Arguments

完整代码

嗯 下面给出完整代码:

#-*- coding: utf-8 -*-
'''
Created on 31.3, 2017
pachong

Input:      url

Output:     data

@author: xl
'''
import urllib2
import httplib
import re
from pybloomfilter import BloomFilter
import os
from lxml import html
#print  'ww'
request_headers = {
    'host': "www.mafengwo.cn",
    'connection': "keep-alive",
    'cache-control': "no-cache",
    'upgrade-insecure-requests': "1",
    'user-agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36",
    'accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    'accept-language': "zh-CN,en-US;q=0.8,en;q=0.6"
}
dirname = 'mafengwo_city_notes/'
city_code = []
#download_bf =[]
#defin  bloomfilter
download_bf = BloomFilter(1024*1024*16,0.01)
def download_city_notes(citycode):
    for i in range(1,999):
        url = "http://www.mafengwo.cn/yj/%s/1-0-%d.html" % (citycode,i)
        #if already get
        if url in download_bf:
            continue
            pass
        print 'start open url %s' % (url)
        download_bf.add(url)
        req_yj = urllib2.Request(url,headers= request_headers)
        #print 111
        response_yj = urllib2.urlopen(req_yj)
        #print 222
        htmlcontent_yj = response_yj.read()

        city_notes_url = re.findall('href="/i/\d{7}.html',htmlcontent_yj)
        # if notes is end
        if len(city_notes_url)  == 0:
            return
        for city_note_url in city_notes_url:
            try:
                notes_url = 'http://www.mafengwo.cn%s'%(city_note_url[6:])
                if notes_url in download_bf:
                    continue
                    pass
                print 'download %s'%(notes_url)
                req_note = urllib2.Request(notes_url,headers=request_headers)
                response_note = urllib2.urlopen(req_note)

                htmlcontent_note = response_note.read()
                '''
                print htmlcontent_note
                htmlcontent_note_t = html.parse(htmlcontent_note)
                title = htmlcontent_note_t.find('.//title').text
                '''
                filename = city_note_url[7:].replace('/','_')
                #filename = title
                #save html
                fo = open("%s%s"%(dirname,filename), 'wb+')
                fo.write(htmlcontent_note)
                fo.close()
                download_bf.add(notes_url)
            except Exception,Arguments:
                print Arguments
                continue
                pass
            pass
        pass
    pass





if __name__ == '__main__':
    if not os.path.exists(dirname):
        os.makedirs(dirname)
        pass
    try:
        #get city code
        req = urllib2.Request("http://www.mafengwo.cn/mdd/",headers=request_headers)
        response = urllib2.urlopen(req)
        htmlcontent = response.read()
        # get city homepage
        city_home_pages = re.findall('/travel-scenic-spot/mafengwo/\d{5}.html',htmlcontent)
        for city in city_home_pages:
            city_code.append(city[29:34])
            download_city_notes(city[29:34])
            pass
        print k
    except urllib2.HTTPError,Arguments:
        print Arguments
    except httplib.BadStatusLine:
        print 'BadStatusLine'
    except Exception,Arguments:
        print Arguments

运行结果

一、爬虫入门知识_第7张图片
一、爬虫入门知识_第8张图片

你可能感兴趣的:(爬虫,http协议,分布式,py2-爬虫)