分布式爬虫实战(一)互联网协议相关基础知识

HTTP 协议

HTTP HEADER

  • HTTP REQUEST 的 Header 部分,需要重点设置某些参数

 Accept: text/plain
 Accept-Charset: utf-8
 Accept-Encoding: gzip, deflate
 Accept-Language: en-US
 Connection: keep-alive
 Content-Length: 348
 Content-Type: application/x-www-form-urlencoded
 Date: Tue, 15 Nov 1994 08:12:31 GMT
 Host: en.wikipedia.org:80
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Cookie: CP=H2; WMF-Last-Access=10-Jul-2017; WMF-Last-Access-Global=10-Jul-2017; GeoIP=HK:HCW:Hong_Kong:22.28:114.15:v4

  • HTTP RESPONSE 的 HEADER 中需要注意的

 Accept-Patch: text/example;charset=utf-8
 Cache-Control: max-age=3600
 Content-Encoding: gzip
 Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT  Content-Language: da
 Content-Length: 348
 ETag: “737060cd8c284d8af7ad3082f209582d“
 Expires: Thu, 01 Dec 1994 16:00:00 GMT
 Location: http://www.w3.org/pub/WWW/People.html
 Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1  Status: 200 OK

  • 使用 Chrome 的 Postman 插件抓包

我常用的抓包的工具:firebug(似乎已经停止维护)、Postman、Wireshark。

Postman 的优势是使用简单、可以自动生成一些代码、可以伪造/重发请求等。

安装 Chrome 的 Postman 插件之后打开 Inspector ,点击需要查看的请求,可以看到右边面板出现请求/响应的数据:

这里写图片描述

右边面板的 “ Code ” 按钮可以生成请求代码 :

这里写图片描述

响应码

每个 HTTP 请求都会返回一个状态码

  • 2XX : 成功
  • 3XX : 跳转
  • 4XX : 客户端错误
  • 5XX : 服务器端错误
  •  400 Bad Request 客户端请求有语法错误,不能被服务器所理解
  •  401 Unauthorized 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用  403 Forbidden 服务器收到请求,但是拒绝提供服务
  •  404 Not Found 请求资源不存在,eg:输入了错误的URL
  • 500 Internal Server Error 服务器发生不可预期的错误
  •  503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常
  •  300 Multiple Choices 存在多个可用的资源,可处理或丢弃
  •  301 Moved Permanetly 重定向
  •  302 Found 重定向
  •  304 Not Modified 请求的资源未更新,丢弃

宽度优先和深度优先

对于宽度优先和深度优先哪个好这个问题,答案是:It depends.

一般情况下,宽度优先会更好 ,理由是:

  • 重要的网页和种子节点的距离较近
  • 宽度优先有利于多爬虫并发协作
  • 万维网深度一般不会很深,有很多路径可以到达

当然,套路的说法是:宽度优先和深度优先结合。

宽度优先示例代码:

import urllib2
from collections import deque
import json
from lxml import etree
import httplib
import hashlib
from pybloomfilter import BloomFilter

class CrawlBSF:
    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"
        }

    cur_level = 0
    max_level = 5
    dir_name = 'iterate/'
    iter_width = 50
    downloaded_urls = []

    du_md5_file_name = dir_name + 'download.txt'
    du_url_file_name = dir_name + 'urls.txt'

    download_bf = BloomFilter(1024*1024*16, 0.01)

    cur_queue = deque()
    child_queue = deque()

    def __init__(self, url):
        self.root_url = url
        self.cur_queue.append(url)
        self.du_file = open(self.du_url_file_name, 'a+')
        try:
            self.dumd5_file = open(self.du_md5_file_name, 'r')
            self.downloaded_urls = self.dumd5_file.readlines()
            self.dumd5_file.close()
            for urlmd5 in self.downloaded_urls:
                self.download_bf.add(urlmd5[:-2])
        except IOError:
            print "File not found"
        finally:
            self.dumd5_file = open(self.du_md5_file_name, 'a+')

    def enqueueUrl(self, url):
        self.child_queue.append(url)

    def dequeuUrl(self):
        try:
            url = self.cur_queue.popleft()
            return url
        except IndexError:
            self.cur_level += 1
            if self.cur_level == self.max_level:
                return None
            if len(self.child_queue) == 0:
                return None
            self.cur_queue = self.child_queue
            self.child_queue = deque()
            return self.dequeuUrl()

    def getpagecontent(self, cur_url):
        print "downloading %s at level %d" % (cur_url, self.cur_level)
        try:
            req = urllib2.Request(cur_url, headers=self.request_headers)
            response = urllib2.urlopen(req)
            html_page = response.read()
            filename = cur_url[7:].replace('/', '_')
            fo = open("%s%s.html" % (self.dir_name, filename), 'wb+')
            fo.write(html_page)
            fo.close()
        except urllib2.HTTPError, Arguments:
            print Arguments
            return
        except httplib.BadStatusLine:
            print 'BadStatusLine'
            return
        except IOError:
            print 'IO Error at ' + filename
            return
        except Exception, Arguments:
            print Arguments
            return
        # print 'add ' + hashlib.md5(cur_url).hexdigest() + ' to list'
        dumd5 = hashlib.md5(cur_url).hexdigest()
        self.downloaded_urls.append(dumd5)
        self.dumd5_file.write(dumd5 + '\r\n')
        self.du_file.write(cur_url + '\r\n')
        self.download_bf.add(dumd5)

        html = etree.HTML(html_page.lower().decode('utf-8'))
        hrefs = html.xpath(u"//a")

        for href in hrefs:
            try:
                if 'href' in href.attrib:
                    val = href.attrib['href']
                    if val.find('javascript:') != -1:
                        continue
                    if val.startswith('http://') is False:
                        if val.startswith('/'):
                            val = 'http://www.mafengwo.cn' + val
                        else:
                            continue
                    if val[-1] == '/':
                        val = val[0:-1]
                    # if hashlib.md5(val).hexdigest() not in self.downloaded_urls:
                    if hashlib.md5(val).hexdigest() not in self.download_bf:
                        self.enqueueUrl(val)
                    else:
                        print 'Skip %s' % (val)
            except ValueError:
                continue

    def start_crawl(self):
        while True:
            url = self.dequeuUrl()
            if url is None:
                break
            self.getpagecontent(url)
        self.dumd5_file.close()
        self.du_file.close()

crawler = CrawlBSF("http://www.mafengwo.cn")
crawler.start_crawl()

不重复抓取策略

因为一个网页包含的链接可能已经被抓取过,这个时候就需要对待爬取的URL队列进行去重。考虑如下策略:

  • 数据库 : 将访问过的URL保存到数据库。这种方法效率太低。
  • HashSet : 用HashSet将访问过的URL保存起来。那只需接近O(1)的代价就可以查到一个URL是否被访问过了。这种方法快但是消耗内存(土豪随意)。
  • 单向哈希再 HashSet : URL经过MD5或SHA-1等单向哈希后再保存到HashSet或数据库。
  • Bit-Map : 建立一个BitSet,将每个URL经过一个哈希函数映射到某一位。
  • 使用 Redis : 真正的项目里边会使用 Redis 之类的数据库进行单项哈希再保存,检验完成再决定是否抓取

网站分析

  • 评估网页规模

这里写图片描述

  • 使用 SiteMap 分析网站结构

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值