Python_爬虫基础

发布时间:2019-09-11 11:13:08编辑:auto阅读(156)

    爬虫概念

    数据获取的方式:

    • 企业生产的用户数据:大型互联网公司有海量用户,所以他们积累数据有天然优势。有数据意识的中小型企业,也开始积累的数据。
    • 数据管理咨询公司
    • 政府/机构提供的公开数据
    • 第三方数据平台购买数据
    • 爬虫爬取数据
    什么是爬虫

    抓去网页数据的程序

    网页三大特征:

    • 每个网页都有自己的URL
    • 网页都使用HTML标记语言来描述页面信息
    • 网页都使用HTTP/HTTPS协议来传输HTML数据
    爬虫的设计思路
    1. 确定需要爬取的网页URL地址
    2. 通过HTTP/HTTPS协议来获取对应的HTML页面
    3. 提取HTML页面中的数据
      如果是需要的数据,就保存起来
      如果页面是其它URL,那就继续爬取

    如何抓取HTML页面
    HTTP协议请求的处理,urllib, urllib2, requests,处理后的请求可以模拟浏览器发送请求,获取服务器响应的文件

    解析服务器响应的内容
    re, xpath(常用), BeautifulSoup4(bs4), jsonpath, pyquery等使用某种描述性一样来给需要提取的数据定义一个匹配规则,符合这个规则的数据就会被匹配。

    如何采集动态HTML,验证码的处理
    Selenium(自动化测试工具) + PhantomJS(无界面浏览器)
    验证码处理通过Tesseract: 机器图像识别系统(图片中的文本识别)

    Scrapy框架
    (Scrapy, Pyspider)

    • 高性能高定制型(异步网络框架twisted),所以数据下载速度快
    • 提供了数据存储,数据下载,提取规则等组件

    分布式策略

    1. 是否有那么多的机器去做分布式?
    2. 获取的数据是否值得搭建分布式系统?

    使用scrapy-redis来搭建,在Scrapy的基础上添加了一套 Redis数据库为核心的一套组件,让Scrapy框架支持分布式的功能。主要在Redis中做请求指纹去重请求分配数据临时存储

    爬虫 - 反爬虫 - 反反爬虫

    反爬虫: User-Agent, IP, 代理, 验证码, 动态数据加载, 加密数据
    数据的价值,是否值得去费劲去做反爬虫,一般做到代理阶段或封IP
    机器成本 + 人力成本 > 数据价值

    爬虫和反爬虫之间的斗争,最后一定是爬虫获胜。
    只要是真实用户可以浏览的网页数据,爬虫就一定能爬下来。(爬虫模拟浏览器获取数据)

    爬虫集合awesome-spider

    通用爬虫

    搜索引擎使用的爬虫系统

    目标:尽可能把互联网上所有的网页下载下来,放到本地服务器里形成备份,再对这些网页做相关处理(提取关键字,去掉广告),最后提供一个用户检索接口

    抓取流程:

    • 首先选取一部分已有的URL,把这些URL放到待爬取队列。
    • 从队列里去取出这些URL,然后解析DNS得到主机IP,然后去这个IP对应的服务器下载HTML页面,保存到搜索引擎的本地服务器里,之后把这个已经爬过的URL放入到已经爬取队列中
    • 分析网页内容,找出网页中的其它URL内容,继续爬取。

    搜索引擎如何获取一个新网站的URL:

    • 主动向搜索引擎提交网址: 百度搜索资源平台
    • 在其它网站设置外链
    • 搜索引擎会和DNS服务商进行合作,可以快速收录新的网站

    通用爬虫并不是万物皆可爬,它也需要遵守规则:
    Robots协议,协议会指明通用爬虫可以爬取网页的权限。
    Robots.txt并不是所有爬虫都遵守,一般只有大型搜索引擎爬虫才会遵守。

    通用爬虫工作流程:
    爬取网页 -> 存储数据 -> 内容处理 -> 提供检索/排名服务

    搜索引擎排名:

    • PageRank值:根据网站的流量(pv),流量越高,排名约靠前
    • 竞价排名

    通用爬虫的缺点:

    1. 只能提供和文本相关的内容(HTML,Word,PDF)等,但是不能提供多媒体(音乐,视频,图片)和二进制文件。
    2. 提供的结果千篇一律,不能针对不同领域的人提供不同的搜索结果。
    3. 不能理解人类语义上的检索。

    DNS: 把域名解析成IP

    聚焦爬虫

    爬虫程序员写的针对某种内容爬虫。(针对通用爬虫的缺点)

    面向主题爬虫,面向需求爬虫,会针对某种特定的内容去爬取信息,而且会保证内容信息和需求尽可能相关。

    HTTP&HTTPS

    HTTP协议(HyperText Transfer Protocol,超文本传输协议):是一种发布和接收HTML页面的方法。

    HTTPS(Hypertext Transfer Protocol over Secure Socket Layer)简单讲是HTTP的安全版,在HTTP下加入SSL

    SSL(Secure Sockets Layer 安全套接层)主要用于Web的安全传输协议,在传输层对网络连接进行加密,保障在Internet上数据传输的安全。

    • HTTP的端口号为80
    • HTTPS的端口号为443

    HTTP工作原理

    网络爬虫抓取过程可以理解为模拟浏览器操作的过程

    浏览器的主要功能是向服务器发出请求,在浏览器窗口中展示您选择的网络资源,HTTP是一套计算机通过网络进行通信的规则。

    常用的请求报头:

    • Host (主机和端口号): 对应网址URL中的Web名称和端口号,用于指定被请求资源的Internet主机和端口号,通常属于URL的一部分。
    • Connection (链接类型): 表示客户端与服务连接类型

      1. Client 发起一个包含 Connection:keep-alive 的请求,HTTP/1.1使用 keep-alive 为默认值。
      2. Server收到请求后:如果 Server 支持 keep-alive,回复一个包含 Connection:keep-alive 的响应,不关闭连接; 如果 Server 不支持keep-alive,回复一个包含 Connection:close 的响应,关闭连接。
      3. 如果client收到包含 Connection:keep-alive 的响应,向同一个连接发送下一个请求,直到一方主动关闭连接。
      4. keep-alive在很多情况下能够重用连接,减少资源消耗,缩短响应时间,比如当浏览器需要多个文件时(比如一个HTML文件和相关的图形文件),不需要每次都去请求建立连接。
    • Upgrade-Insecure-Requests (升级为HTTPS请求): 升级不安全的请求,意思是会在加载 http 资源时自动替换成 https 请求,让浏览器不再显示https页面中的http请求警报。(HTTPS 是以安全为目标的 HTTP 通道,所以在 HTTPS 承载的页面上不允许出现 HTTP 请求,一旦出现就是提示或报错。)
    • User-Agent (浏览器名称): 是客户浏览器的名称
    • Accept (传输文件类型): 指浏览器或其他客户端可以接受的MIME(Multipurpose Internet Mail Extensions(多用途互联网邮件扩展))文件类型,服务器可以根据它判断并返回适当的文件格式。

      1. Accept: */*:表示什么都可以接收。
      2. Accept:image/gif:表明客户端希望接受GIF图像格式的资源;
      3. Accept:text/html:表明客户端希望接受html文本。
      4. Accept: text/html, application/xhtml+xml;q=0.9, image/*;q=0.8:表示浏览器支持的 MIME 类型分别是 html文本、xhtml和xml文档、所有的图像格式资源。html中文件类型的accept属性有哪些
    • Referer (页面跳转处): 表明产生请求的网页来自于哪个URL,用户是从该 Referer页面访问到当前请求的页面。这个属性可以用来跟踪Web请求来自哪个页面,是从什么网站来的等。有时候遇到下载某网站图片,需要对应的referer,否则无法下载图片,那是因为人家做了防盗链,原理就是根据referer去判断是否是本网站的地址,如果不是,则拒绝,如果是,就可以下载;
    • Accept-Encoding(文件编解码格式): 指出浏览器可以接受的编码方式。编码方式不同于文件格式,它是为了压缩文件并加速文件传递速度。浏览器在接收到Web响应之后先解码,然后再检查文件格式,许多情形下这可以减少大量的下载时间。例如:Accept-Encoding:gzip;q=1.0, identity; q=0.5, *;q=0
    • Accept-Language(语言种类): 指出浏览器可以接受的语言种类,如en或en-us指英语,zh或者zh-cn指中文,当服务器能够提供一种以上的语言版本时要用到。
    • Accept-Charset(字符编码): 指出浏览器可以接受的字符编码。例如:Accept-Charset:iso-8859-1,gb2312,utf-8
    • Cookie (Cookie): 浏览器用这个属性向服务器发送Cookie。Cookie是在浏览器中寄存的小型数据体,它可以记载和服务器相关的用户信息,也可以用来实现会话功能
    • Content-Type (POST数据类型): POST请求里用来表示的内容类型。例如:Content-Type = Text/XML; charset=gb2312:

    常用的响应报头(了解):

    • Cache-Control:must-revalidate, no-cache, private: 告诉客户端,服务端不希望客户端缓存资源,在下次请求资源时,必须要从新请求服务器,不能从缓存副本中获取资源。
    • Connection:keep-alive: 客户端服务器的tcp连接也是一个长连接,客户端可以继续使用这个tcp连接发送http请求
    • Content-Encoding:gzip: 告诉客户端,服务端发送的资源是采用gzip编码的,客户端看到这个信息后,应该采用gzip对资源进行解码。
    • Content-Type:text/html;charset=UTF-8: 告诉客户端,资源文件的类型,还有字符编码,客户端通过utf-8对资源进行解码,然后对资源进行html解析。
    • Date:Sun, 21 Sep 2016 06:18:21 GMT: 服务端发送资源时的服务器时间,GMT是格林尼治所在地的标准时间。http协议中发送的时间都是GMT的,这主要是解决在互联网上,不同时区在相互请求资源的时候,时间混乱问题。
    • Expires:Sun, 1 Jan 2000 01:00:00 GMT: 这个响应头也是跟缓存有关的,告诉客户端在这个时间前,可以直接访问缓存副本,很显然这个值会存在问题,因为客户端和服务器的时间不一定会都是相同的,如果时间不同就会导致问题。所以这个响应头是没有Cache-Control:max-age=*这个响应头准确的,因为max-age=date中的date是个相对时间.
    • Pragma:no-cache: 这个含义与Cache-Control等同。
    • Server:Tengine/1.4.6: 这个是服务器和相对应的版本,只是告诉客户端服务器的信息。
    • Transfer-Encoding:chunked: 这个响应头告诉客户端,服务器发送的资源的方式是分块发送的。

    响应状态码:

    • 100~199:表示服务器成功接收部分请求,要求客户端继续提交其余请求才能完成整个处理过程。
    • 200~299:表示服务器成功接收请求并已完成整个处理过程。常用200(OK 请求成功)。
    • 300~399:为完成请求,客户需进一步细化请求。例如:请求的资源已经移动一个新地址、常用302(所请求的页面已经临时转移至新的url)、307和304(使用缓存资源)。
    • 400~499:客户端的请求有错误,常用404(服务器无法找到被请求的页面)、403(服务器拒绝访问,权限不够)。
    • 500~599:服务器端出现错误,常用500(请求未完成。服务器遇到不可预知的情况)。

    Cookie 和 Session:
    因为服务器和客户端的交互仅限于请求/响应过程,结束之后便断开,在下一次请求时,服务器会认为新的客户端。为了维护他们之间的链接,让服务器知道这是前一个用户发送的请求,必须在一个地方保存客户端的信息

    Cookie:通过在客户端 记录的信息确定用户的身份。
    Session:通过在服务器端 记录的信息确定用户的身份。

    urllib

    urllib.request

    linux中的py源码文件位置:
    python自带:vim /usr/lib/python2.7/urllib2.py
    pip安装:vim /usr/local/lib/python3.6/site-packages/django/http/cookie.py

    urllib2.urlopen
    # -*- coding:utf-8 -*-
    
    import urllib.request as urllib2
    
    # 返回类文件对象
    response = urllib2.urlopen('http://www.baidu.com/')
    # urlopen不支持构造
    
    # 服务器返回类文件对象支持python文件对象的操作方法
    # read()方法就是读取文件里面的全部内容,返回字符串
    html = response.read()
    
    print(html)
    Request
    # -*- coding:utf-8 -*-
    
    import urllib.request as urllib2
    
    
    ua_headres = {
        'User_Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Mobile Safari/537.36'
    }
    
    # urllib2.Request(url, data, headres)
    # 通过urllib2.Request()方法构造一个请求对象
    requset = urllib2.Request('http://www.baidu.com', headers=ua_headres)
    
    
    # 返回类文件对象, urlopen不支持构造
    response = urllib2.urlopen(requset)
    
    # 服务器返回类文件对象支持python文件对象的操作方法
    # read()方法就是读取文件里面的全部内容,返回字符串
    html = response.read()
    
    print(html)

    User_Agent,是发送请求必须带的请求头

    Response响应

    response是服务器响应的类文件,除了支持文件操作的方法外,常用的方法也有:
    respnse.getcode(), response.geturl(), response.info()

    #condig=utf-8
    
    import urllib.request as urllib2
    
    # print(dir(urllib2))
    
    ua_headres = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.4620.400 QQBrowser/9.7.13014.400'        
    }
    
    request = urllib2.Request('http://www.baidu.com/', headers=ua_headres)
    
    response = urllib2.urlopen(request)
    
    html = response.read()
    
    # 返回HTTP的响应码,成功返回200
    # 4 服务器页面出错, 5 服务器问题
    print(response.getcode())
    
    # 返回实际数据的url,防止重定向问题
    print(response.geturl())
    
    # 返回服务器响应报头信息
    print(response.info())
    
    # print(dir(response))
    User-Agent历史

    如果用一个合法的身份去请求别人网站,就是欢迎的,所以就应该给这个代码加上一个身份,就是所谓的User-Agent头。

    urllib2默认的User-Agent头为:Python-urllib/x.yxyPython主版本和次版本号,例如 Python-urllib/2.7

    Mosaic世界上第一个浏览器:美国国家计算机应用中心
    Netscape,网景:Netscape(支持框架)
    Microsoft微软:Internet Explorer

    第一次浏览器大战:网景公司失败

    Mozilla 基金组织:Firefox 火狐 内核(Gecko内核)(浏览器支持内核开始,User-Agent开始逐渐使用)

    User-Agent 决定用户的浏览器,为了获取更好的HTML页面效果

    IE就给自己披着了个Mozilla的外皮

    内核:

    • Mozilla: Firefox (Gecko)
    • IE: Trident
    • Opera: Presto
    • Linux: KHTML (like Gecko)
    • Apple: Webkit (like KTML)
    • Google: Chrome (like webkit)
    add_header() & get_header()

    add_header(): 添加/修改 一个HTTP报头
    get_header(): 获取一个已有的HTTP报头值,只能第一个字母大写,其它的必须小写

    # -*- coding:utf-8 -*-
    
    import urllib.request as urllib2
    import random
    
    url = 'http://www.baidu.com/'
    
    # 可以是User-Agent列表,也可以是代理列表。 作用:反反爬虫
    ua_list = [
        'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
        'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2',
        'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36',
        'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50'
    ]
    
    # 在User-Agent列表里随机选择一个User-Agent
    user_agent = random.choice(ua_list)
    
    # 构造一个请求
    request = urllib2.Request(url)
    
    # add_header()方法,添加/修改 一个HTTP报头
    request.add_header('User-Agent', user_agent)
    
    # get_header() 获取一个已有的HTTP报头值,只能第一个字母大写,其它的必须小写
    request.get_header('User-agent')
    
    response = urllib2.urlopen(request)
    
    html = response.read()
    print(html)
    urllib.urlencode

    编码:
    urlencode位置:urllib.parse.urlencode(values)。 其中values所需要编码的数据,参数只能为字典
    解码:
    unquote: urllib.parse.unquote(values)

    #conding=utf-8
    
    import urllib.parse
    
    test = {
        'test': '我的'
    }
    
    # 通过urllib.urlencode()方法,将字典键值对按URL编码转换,从而能被web服务器接受。
    enCodeTest = urllib.parse.urlencode(test)
    
    # 冒号解析为等号
    print(enCodeTest) # test=%E6%88%91%E7%9A%84
    
    # 通过urllib.unquote()方法,把 URL编码字符串,转换回原先字符串。
    print(urllib.parse.unquote(enCodeTest)) # test=我的
    爬取百度贴吧起始页到结束页的案例
    #conding=utf-8
    
    import urllib.request
    import urllib.parse
    
    def loadPage(url, filename):
        '''
            作用: 根据url发送请求,获取服务器响应文件
            url: 需要爬取的url地址
            filename: 处理的文件名
        '''
        print('正在下载' + filename)
        headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
        }
        request = urllib.request.Request(url, headers=headers)
        return urllib.request.urlopen(request).read()
        
    
    def writePage(html, filenmae):
        '''
            作用:将html内容写入到本地
            html: 服务器响应文件内容
        '''
        print('正在保存' + filenmae)
        # 文件写入
        with open(filenmae, 'w') as f: #  with 之后,不需要做文件关闭还有其它上下文处理的操作 等同于 open(), write(), close()
            f.write(html.decode('utf-8'))
        print('-' * 30)    
        print('thanks')
    
    
    def tiebaSpider(url, beginPage, endPage):
        '''
            作用: 贴吧爬虫调度器,负责组合处理
            url: 贴吧url的前部分
            beginPage: 起始页
            endPage: 结束页
        '''
        for page in range(beginPage, endPage+1):
            pn = (page - 1) * 50
            filename = '第' + str(page) + '页.html'
            fullurl = url + '&pn=' + str(pn)
            # print(fullurl)
            html = loadPage(fullurl, filename)
            writePage(html, filename)
            # print(html)
    
    if __name__ == '__main__':
        kw = input('请输入需要爬取的贴吧名:')
        beginPage = int(input('请输入起始页:'))
        endPage = int(input('请输入结束页:'))
        
        # https://tieba.baidu.com/f?ie=utf-8&kw=javascirpt&fr=search
        url = 'https://tieba.baidu.com/f?'
        key = urllib.parse.urlencode({'kw': kw})
        fullurl = url + key
    
        tiebaSpider(fullurl, beginPage, endPage)
    
    POST请求的模拟

    GetPost请求的区别:

    • Get请求:查询参数在QueryString里保存
    • Post请求:查询参数在FormData中保存

    Post请求:

    # -*- coding:utf-8 -*-
    
    import urllib.request
    import urllib.parse
    
    url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
    
    key = input('请输入查询翻译的文字:')
    
    # 发送到服务器的表单数据,如果是中文需要转码
    fromdata = {
        'i': key,
        'from': 'AUTO',
        'to': 'AUTO',
        'smartresult': 'dict',
        'client': 'fanyideskweb',
        'salt': '1528127663128',
        'sign': 'c060b56b628f82259225f751c12da59a',
        'doctype': 'json',
        'version': '2.1',
        'keyfrom': 'fanyi.web',
        'action': 'FY_BY_REALTIME',
        'typoResult': 'false'
    }
    
    data = urllib.parse.urlencode(fromdata).encode('utf-8')
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
    }
    
    request = urllib.request.Request(url, data=data, headers=headers)
    
    html = urllib.request.urlopen(request).read().decode()
    
    print(html)
    获取AJAX加载的内容

    AJAX一般返回的是JSON,直接对AJAX地址进行postget,就返回JSON数据了。

    “作为一名爬虫工程师,最需要关注的是数据的来源”

    # -*- coding:utf-8 -*-
    
    import urllib.request
    import urllib.parse
    
    
    url = 'https://movie.douban.com/j/search_subjects?'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
    }
    formdata = {
        'type': 'movie',
        'tag': '热门',
        'sort': 'recommend',
        'page_limit': 20,
        'page_start': 40
    }
    
    data = urllib.parse.urlencode(formdata).encode('utf-8')
    request = urllib.request.Request(url, data=data, headers=headers)
    html = urllib.request.urlopen(request).read().decode()
    
    print(html)
    处理HTTPS请求 SSL证书验证

    网站的SSL证书是经过CA认证的,则能够正常访问
    单独处理SSL证书,让程序忽略SSL证书验证错误

    # -*- coding:utf-8 -*-
    
    import urllib.request
    import ssl
    
    # 表示忽略未经核实的SSL证书认证
    context = ssl._create_unverified_context()
    
    url = "https://www.12306.cn/mormhweb/"
    
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"
    }
    
    request = urllib.request.Request(url, headers = headers)
    
    # 在urlopen()方法里 指明添加 context 参数
    response = urllib.request.urlopen(request, context = context)
    
    print(response.read())

    CA: 数字证书认证中心的简称,是指发放、管理、废除数字证书的受信任的第三方机构(类似与身份证)
    CA的作用: 检查证书持有者身份的合法性,并签发证书,以防证书被伪造或篡改,以及对证书和密钥进行管理

    一般正常的网站都会主动出示自己的数字证书,来确保客户端和网站服务器之间的通信数据是加密安全的.

    Handler和Opener的使用

    自定义Handler

    # -*- coding:utf-8 -*-
    
    import urllib.request
    
    # 构建一个HTTPHandler处理器对象,支持处理HTTP的请求
    # http_hander = urllib.request.HTTPHandler()
    http_hander = urllib.request.HTTPHandler(debuglevel=1)
    
    # 调用build_opener()方法构建一个自定义的opener对象,参数是构建的处理器对象
    opener = urllib.request.build_opener(http_hander)
    
    req = urllib.request.Request('http://www.baidu.com')
    
    res = opener.open(req)
    
    print(res.read().decode())
    
    开放代理和私密代理

    高匿:无法拿到真正的物理ip,只能获取代理服务器ip
    透明:能看到代理服务器ip,也可以看到物理ip地址
    快代理
    西刺免费代理

    使用代理IP,这是爬虫/反爬虫的第二大招,通常也是最好用的。
    很多网站会检测某一段时间某个IP的访问次数(通过流量统计,系统日志等),如果访问次数多的不像正常人,它会禁止这个IP的访问。
    可以设置一些代理服务器,每隔一段时间换一个代理,就算IP被禁止,依然可以换个IP继续爬取。

    # -*- coding:utf-8 -*-
    
    import urllib.request
    
    
    # 代理开关,是否启用代理
    proxyswitch = True
    
    # 公开代理
    proxy_ip = {
      'http': '123.57.217.208:3128'
    }
    
    # 私密代理 授权的账号密码
    # proxy_ip_auth = {
    #   'http': 'user:passwd@ip:prot'
    # }
    
    # 构建一个handler对象,参数是一个字典类型,包括代理类型和代理服务器ip+prot
    http_proxy_handler = urllib.request.ProxyHandler(proxy_ip)
    
    # 构建一个没有代理对象的处理器对象
    null_proxy_headler = urllib.request.ProxyHandler({})
    
    
    if proxyswitch:
      opener = urllib.request.build_opener(http_proxy_handler)
    else:
      opener = urllib.request.build_opener(null_proxy_headler)
    
    # 构建一个全局的opener,之后的所有请求都可以用urlopen()方式发送,也附带Handler功能
    urllib.request.install_opener(opener)
    request = urllib.request.Request('http://www.baidu.com/')
    response = urllib.request.urlopen(request)
    
    # response = opener.open(request)
    
    print(response.read().decode())

    ProxyBasicAuthHandler(代理授权验证):

    #conding=utf-8
    
    import urllib.request
    
    
    user = ''
    passwd = ''
    proxyserver = ''
    
    # 构建一个密码管理对象,用来保存需要处理的用户名和密码
    passwdmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
    
    # 添加账户信息,第一个参数realm是与远程服务器相关的域信息,一般都是写None,后面三个参数分别是 代理服务器、用户名、密码
    passwdmgr.add_password(None, proxyserver, user, passwd)
    
    # 构建一个代理基础用户名/密码验证的ProxyBasicAuthHandler处理器对象,参数是创建的密码管理对象
    proxy_auth_handler = urllib.request.ProxyDigestAuthHandler(passwdmgr)
    
    # 通过 build_opener()方法使用这些代理Handler对象,创建自定义opener对象,参数包括构建的 proxy_handler 和 proxyauth_handler
    opener = urllib.request.build_opener(proxy_auth_handler)
    
    request = urllib.request.Request('http://www.baidu.com/')
    response = opener.open(request)
    
    print(response.read().decode())

    Cookie

    Cookie 是指某些网站服务器为了辨别用户身份和进行Session跟踪,而储存在用户浏览器上的文本文件,Cookie可以保持登录信息到用户下次与服务器的会话。

    HTTP是无状态的面向连接的协议, 为了保持连接状态, 引入了Cookie机制 Cookie是http消息头中的一种属性

    Cookie名字(Name)
    Cookie的值(Value)
    Cookie的过期时间(Expires/Max-Age)
    Cookie作用路径(Path)
    Cookie所在域名(Domain),
    使用Cookie进行安全连接(Secure)。
    
    前两个参数是Cookie应用的必要条件,另外,还包括Cookie大小(Size,不同浏览器对Cookie个数及大小限制是有差异的)。

    Cookie由变量名和值组成,根据 Netscape公司的规定,Cookie格式如下:

    Set-Cookie: NAME=VALUE;Expires=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE

    Python处理Cookie,一般是通过cookielib模块和urllib2模块的HTTPCookieProcessor处理器类一起使用。

    cookielib模块:主要作用是提供用于存储cookie的对象

    HTTPCookieProcessor处理器:主要作用是处理这些cookie对象,并构建handler对象。

    cookielib 库

    该模块主要的对象有CookieJarFileCookieJarMozillaCookieJarLWPCookieJar

    一般情况下,只用CookieJar(),如果需要和本地文件交互,就需要使用MozillaCookjar()LWPCookieJar()

    获取Cookie,并保存到CookieJar()对象中:

    #!/usr/local/bin/python
    
    import urllib.request
    import http.cookiejar
    
    cookiejar = http.cookiejar.CookieJar()
    
    http_handler = urllib.request.HTTPCookieProcessor(cookiejar)
    
    opener = urllib.request.build_opener(http_handler)
    
    opener.open('http://www.baidu.com')
    
    cook_str = ''
    for item in cookiejar:
        cook_str = cook_str + item.name + '=' + item.value + ';'
    
    print(cook_str[:-1])
    
    # BAIDUID=5DB0FC0C0DC9692BB8EE6EDC93A2EDEA:FG=1;BIDUPSID=5DB0FC0C0DC9692BB8EE6EDC93A2EDEA;H_PS_PSSID=1468_26259_21099_26350_26580;PSTM=1528615563;BDSVRTM=0;BD_HOME=0

    访问网站获得cookie,并把获得的cookie保存在cookie文件中:

    #!/usr/local/bin/python
    
    import http.cookiejar
    import urllib.request
    
    filename = 'cookie.txt'
    
    # 声明一个MozillaCookieJar(有save实现)对象实例来保存cookie,之后写入文件
    cookiejar = http.cookiejar.MozillaCookieJar(filename)
    
    handler = urllib.request.HTTPCookieProcessor(cookiejar)
    opener = urllib.request.build_opener(handler)
    req = opener.open('http://www.baidu.com')
    
    # 保存cookie到本地文件
    cookiejar.save()
    
    print(1)

    非结构化数据和结构化数据

    实际上爬虫一共就四个主要步骤:

    • 明确目标 (要知道准备在哪个范围或者网站去搜索)
    • 爬 (将所有的网站的内容全部爬下来)
    • 取 (去掉对没用处的数据)
    • 处理数据(按照想要的方式存储和使用)
    re模块
    pattern = re.compile(regExp)
    
    pattern.match(): 从起始位置开始查找,返回第一个符合规则的,只匹配一次。
    pattern.search(): 从任意位置开始查找,返回第一个符合规则的,只匹配一次。
    pattern.findall(): 所有的全部匹配,返回列表
    pattern.split(): 分割字符串,返回列表
    pattern.sub(): 替换
    rs.I 忽略大小写
    re.S 全文匹配

    match(str, begin, end):

    >>> pattern = re.compile(r'([a-z]+) ([a-z]+)', re.I)
    >>> m = pattern.match('hello world hello python')
    >>> m.group()
    'hello world'
    >>> m.group(1)
    'hello'
    >>> m.group(2)
    'world'

    findall(str, begin, end):

    >>> pattern = re.compile(r'\d+')
    >>> pattern.findall('hello world 123 456 789')
    ['123', '456', '789']
    >>> 

    split(str, count):

    >>> pattern = re.compile(r'[\s\d\;]+')
    >>> pattern.split('a b\a;m; a  ')
    ['a', 'b\x07', 'm', 'a', '']
    >>> 
    >>> pattern = re.compile('[\s\d\;]+')
    >>> pattern.split(r'a b\a;m; a  ')
    ['a', 'b\\a', 'm', 'a', '']
    >>> 

    sub():

    >>> pattern = re.compile(r'(\w+)(\w+)')
    >>> strs = 'hello 123, world 456'
    >>> pattern.sub('hello world', strs)
    'hello world hello world, hello world hello world'
    >>>

    xpath

    chrome插件:XPath Helper
    XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在XML文档中对元素和属性进行遍历。

    lxml库:
    lxml是 一个HTML/XML的解析器,主要的功能是如何解析和提取HTML/XML数据。

    获取属性:@src, @title, @class
    获取内容: /text()
    模糊查询: contains(@id, '模糊字符串')

    xpath匹配规则:

    //div[@class="pic imgcover"]/img/@src

    xpath模糊匹配:

    //div[contains(@id, 'qiushi_tag')]

    获取某个网站的图片:

    #conding=utf-8
    
    import urllib.request
    import urllib.parse
    from lxml import etree # 我乃河北,姓氏颜良
    
    class getQdailyImg:
        def __init__(self, url):
            self.url = url
    
        def loadPage(self):
            print('正在下载...')
            headres = {
                'User-Agent': 'ie 的user-Agent'
            }
            req = urllib.request.Request(self.url, headers=headres)
            html = urllib.request.urlopen(req).read().decode()
            xmlDom = etree.HTML(html)
            linkList = xmlDom.xpath('//div[@class="pic imgcover"]/img/@src')
            print(linkList)
            self.writePage(linkList)
    
        def writePage(self, data):
            for item in data:
                with open('img.txt', 'w') as f:
                    f.write(item)
    
    if __name__ == '__main__':
        qdI = getQdailyImg('http://www.qdaily.com/')
        qdI.loadPage()

    # -*- coding:utf-8 -*-
    
    import urllib.request
    import json
    from lxml import etree
    
    url = 'http://www.qiushibaike.com/8hr/page/1'
    headers = {
        'user-agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB7.0)'
    }
    
    req = urllib.request.Request(url, headers=headers)
    html = urllib.request.urlopen(req).read()
    text = etree.HTML(html)
    
    # 作为根目录节点
    node_list = text.xpath('//div[contains(@id, "qiushi_tag")]')
    
    items = {}
    for node in node_list:
        username = node.xpath('./div[@class="author clearfix"]//h2/text()')[0]
    
        image = node.xpath('.//div[@class="thumb"]//@src')
    
        content = node.xpath('.//div[@class="content"]/span')[0].text
    
        zan = node.xpath('.//i')[0].text
    
        comment = node.xpath('.//i')[1].text
    
        items = {
            'username': username,
            'image': image,
            'content': content,
            'zan': zan,
            'comment': comment
        }
    
        with open('qiushi.json', 'a') as f:
            f.write(json.dumps(items, ensure_ascii=False) + '\n')
    
    print('ok')

    BeautifulSoup

    Beautiful Soup也是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML数据。

    BeautifulSoup用来解析HTML比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持lxml的 XML解析器。

    pip install bs4
    beautifulsoup4文档

    • tag: BeautifulSoup(html).div
    • attrs: BeautifulSoup(html).div.nameBeautifulSoup(html).div.attres
    • content: BeautifulSoup(html).span.string
    #conding=utf-8
    
    from bs4 import BeautifulSoup
    import requests
    import time
    
    def captchaMethod(captcha_data):
        with open('captcha.jpg', 'wb') as f:
            f.write(captcha_data)
        return input('请输入验证码:')     
    
    def getLoginZhihu():
        # 构建Session对象,保存cookie值
        sess = requests.Session()
    
        headers = {
            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36 QQBrowser/4.3.4986.400'
        }
    
        html = sess.post('https://www.zhihu.com/#sigin', headers=headers).text
        bs = BeautifulSoup(html, 'lxml')
        _xsrf = bs.find('input', attrs={'name': '_xsrf'}).get('value')
    
        captcha_url = 'https://www.zhihu.com/captcha.gif?r=%d&type=login' % (time.time() * 1000)
        captcha = sess.get(captcha_url, headers=headers).content
    
        # 获取验证码文字
        text = captchaMethod(captcha)
    
        data = {
            '_xsrf': _xsrf,
            'email': '123636374@qq.com',
            'password': 'ALARMCHIME',
            'captcha': text
        }
        # 登录 获取cookie
        res = sess.post('https://www.zhihu.com/login/email', data=data, headers=headers).text
    
        res = sess.get('https://www.zhihu.com/people/', headers)
    
    if __name__ == '__main__':
        getLoginZhihu()
        

    JSON和JSONPATH

    JsonJsonPath应用

    • json.loads(): 把Json格式字符串解码转换成Python对象
    • json.dumps(): 实现python类型转化为json字符串,返回一个str对象 把一个Python对象编码转换成Json字符串
    • json.dump(): 将Python内置类型序列化为json对象后写入文件
    • json.load(): 读取文件中json形式的字符串元素 转化成python类型
    dictStr = {"city": "北京", "name": "大猫"}
    print(json.dumps(dictStr, ensure_ascii=False))
    # {"city": "北京", "name": "大刘"}
    
    listStr = [{"city": "北京"}, {"name": "大刘"}]
    json.dump(listStr, open("listStr.json","w"), ensure_ascii=False)
    
    strDict = json.load(open("dictStr.json"))
    print(strDict)
    # {u'city': u'\u5317\u4eac', u'name': u'\u5927\u5218'}

    # -*- coding:utf-8 -*-
    
    import json
    import urllib.request
    import jsonpath
    
    headers = {
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36 QQBrowser/4.3.4986.400'
    }
    url = 'https://www.lagou.com/lbs/getAllCitySearchLabels.json'
    
    request = urllib.request.Request(url, headers=headers)
    
    response = urllib.request.urlopen(request)
    html = response.read().decode()
    
    unicodeStr = json.loads(html)
    content = jsonpath.jsonpath(unicodeStr, '$..name')
    print(content)
    array = json.dumps(content, ensure_ascii=False)
    
    with open('lagoucontent.json', 'w') as f:
        f.write(array)

    多线程爬虫

    一个进程可能包括多个线程,线程之间执行任务,必须通过加锁方式控制它们(阻塞)
    父线程和子线程都关系,只要父线程执行完,不管子线程如何,都一并结束

    1. 计算机的核心是CPU,CPU承担了所有的计算任务
    2. 一个CPU核心一次只能执行一个任务
      多个CPU核心同时可以执行多个任务
    3. 一个CPU一次只能执行一个进程,其它进程处于非运行
    4. 进程里包含的执行单元叫线程
      一个进程 可以包含 多个线程
    5. 一个进程的内存空间是共享的,每个进程里的线程都可以使用这个共享空间
      一个线程在使用这个共享空间的时候,其它线程必须等待它结束
    6. 通过“锁”实现,作用就是防止多个线程使用当前内存空间。
      先使用的线程会加锁,锁上该空间,其它线程就在等待。

    进程:表示程序的一次执行
    线程:CPU运算的基本调度单位

    GIL: Python里的执行通行证,而且只有唯一个。拿到通行证的线程才会执行

    Python 的多线程适用于:大量密集的I/O处理 (单独都任务,一个进程,只能执行一个任务)
    Python 的多进程适用于:大量的密集并行计算

    #conding=utf-8
    
    import json
    import threading
    from queue import Queue
    
    import requests
    from lxml import etree
    
    CREAWL_EXIT = False
    PARSE_EXIT = False
    
    '''
        采集线程
    '''
    class ThreadCrawl(threading.Thread):
        def __init__(self, threadName, pageQueue, dataQueue):
            # threading.Thread.__init__(self)
            super(ThreadCrawl, self).__init__() # 多个父类,多重继承
            self.threadName = threadName
            self.pageQueue = pageQueue
            self.dataQueue = dataQueue
            self.headers = {
                'user-agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB7.0)'
            }
    
        def run(self):
            print('start' + self.threadName)
            while not CREAWL_EXIT:
                try:
                    page = self.pageQueue.get(False)
                    url = 'https://www.qiushibaike.com/8hr/page/' + str(page) + '/'
                    res = requests.get(url, headers=self.headers).text
                    self.dataQueue.put(res)
                except:
                    pass
            print('end' + self.threadName)      
    
    '''
        解析线程
    '''
    class ThreadParse(threading.Thread):
        def __init__(self, threadingName, dataQueue, filename):
            super(ThreadParse, self).__init__()
            self.threadingName = threadingName
            self.dataQueue = dataQueue
            self.filename = filename
    
        def run(self):
            print('start' + self.threadingName)
            while not PARSE_EXIT:
                try:
                    html = self.dataQueue.get(False)
                    self.parse(html)
                except:
                    pass
            print('end' + self.threadingName)
    
    
        def parse(self, html):
            text = etree.HTML(html)
            node_list = text.xpath('//div[contains(@id, "qiushi_tag")]')
            items = {}
            for node in node_list:
                username = node.xpath('./div[@class="author clearfix"]//h2/text()')[0]
                image = node.xpath('.//div[@class="thumb"]//@src')
                content = node.xpath('.//div[@class="content"]/span')[0].text
                zan = node.xpath('.//i')[0].text
                comment = node.xpath('.//i')[1].text
                items = {
                    'username': username,
                    'image': image,
                    'content': content,
                    'zan': zan,
                    'comment': comment
                }
                self.filename.write(json.dumps(items, ensure_ascii=False) + '\n')
    
    
    def main():
        # 页码
        pageQueue = Queue(10)
        # 放入1~10的数字
        for i in range(1, 10+1):
            pageQueue.put(i)
    
        # 采集结果(每页的HTML源码)
        dataQueue = Queue()
    
        filename = open('duanzi.json', 'a')
    
        crawlList = ['采集线程1', '采集线程2', '采集线程3']
    
        threadcrawl = []
        for threadName in crawlList:
            thread = ThreadCrawl(threadName, pageQueue, dataQueue)
            thread.start()
            threadcrawl.append(thread)
        
        parseList = ['解析线程1', '解析线程2', '解析线程3']
        threadparse = []
        for threadName in parseList:
            thread = ThreadParse(threadName, dataQueue, filename)
            thread.start()
            threadparse.append(thread)
        
        # 等待pageQueue队列为空, 或者 数据队列为空,也就是等待之前执行的操作执行完毕
        while not pageQueue.empty() or not dataQueue.empty():
            pass
    
        global CREAWL_EXIT
        CREAWL_EXIT = True
        print('queue队列为空')
    
        global PARSE_EXIT
        PARSE_EXIT = True
        print('data队列为空')
    
        for threadItem in crawlList:
            threadItem.join('')
            print('1')
    
    if __name__ == '__main__':
        main()

    自动化测试unittest模块使用和模拟用户点击抓取数据(拿去ajax分页数据)

    # -*- coding:utf-8 -*-
    
    import unittest
    from selenium import webdriver
    from bs4 import BeautifulSoup as bs
    
    class Douyu(unittest.TestCase):
        def setUp(self):
            self.driver = webdriver.PhantomJS()
    
        # unittest测试方法必须有`test`字样开头
        def testDouyu(self):
            self.driver.get('https://www.douyu.com/directory/all')
            while True:
                soup = bs(self.driver.page_source, 'lxml')
                names = soup.find_all('h3', {'class': 'ellipsis'})
                viewNums = soup.find_all('span', {'class': 'dy-num fr'})
                
                for name, viewNum in zip(names, viewNums):
                    print('房间名' + name.get_text() + '; ' + '观众人数' + viewNum.get_text())
    
                # 在页面源码中找到"下一页"未隐藏的标签,就退出循环
                if self.driver.page_source.find('shark-pager-disable-next') != -1:
                    break
    
                # 一直点击下一页
                self.driver.find_element_by_class_name('shark-pager-next').click()    
    
        # 测试结束执行的方法
        def tearDown(self):
            self.driver.quit()
    
    if __name__ == '__main__':
        unittest.main()

    执行javascript语句:execute_script

    #conding=utf-8
    
    from selenium import webdriver
    import time
    
    driver = webdriver.PhantomJS('/Users/linxingzhang/Library/Python/3.6/lib/python/site-packages/selenium/webdriver/phantomjs')
    driver.get("https://movie.douban.com/typerank?type_name=剧情&type=11&interval_id=100:90&action=")
    
    time.sleep(3)
    # 向下滚动10000像素
    js = "document.body.scrollTop=10000"
    # js="var q=document.documentElement.scrollTop=10000"
    
    # 查看页面快照
    driver.save_screenshot("douban.png")
    
    # 执行JS语句
    driver.execute_script(js)
    time.sleep(10)
    
    # 查看页面快照
    driver.save_screenshot("newdouban.png")
    
    driver.quit()
    投票
    import datetime
    import sys
    import threading
    import time
    from random import choice  # choice() 方法返回一个列表,元组或字符串的随机项
    
    import requests
    from lxml import etree
    
    from fake_useragent import UserAgent  # 引入随机的UA
    
    # 设置user-agent列表,每次请求时,随机挑选一个user-agent
    ua_list = UserAgent()
    
    
    def get_ip():
        '''
            获取代理ip
        '''
        url = 'http://www.xicidaili.com/nn'
        headers = {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
            'Host': 'www.xicidaili.com',
            'Referer': 'http: // www.xicidaili.com/nn',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6726.400 QQBrowser/10.2.2265.400'
        }
        ret = requests.get(url, headers=headers)
        xmlDom = etree.HTML(ret.text)
    
        data = xmlDom.xpath('//table[@id="ip_list"]//tr')
        z = []
        for tr in data:
            if tr.xpath('td'):
                ip = tr.xpath('td')[1].text  # 获取所有IP
                port = tr.xpath('td')[2].text  # 获取所有端口
                z.append(ip + ':' + port)
        return z
    
    
    def get_url(url, code=0, ips=[]):
        '''
            投票
            如果因为代理IP已失效造成投票失败,则会自动换一个代理IP后继续投票
        '''
        try:
            ip = choice(ips)
            print(ip, 'ip' * 5)
        except:
            return False
        else:
            proxies = {
                'http': ip
            }
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                'Host': 'best.zhaopin.com',
                'Origin': 'https: // best.zhaopin.com',
                'Referer': 'https//best.zhaopin.com/?sid=121128100&site=sou',
                'User-Agent': ua_list.random
            }
            print(ua_list.random, 'ua_list' * 5)
    
        try:
            data = {'bestid': '3713', 'score': '5,5,5,5,5,5', 'source': 'best'}
            # 跳过证书的验证 verify=False
            result = requests.post(url, data=data, headers=headers, proxies=proxies)
            print(result, 'result' * 5)
        except requests.exceptions.ConnectionError:
            print('ConnectionError')
            if not ips:
                print('ip 失效')
                sys.exit()
    
            # 删除不可用的代理IP
            if ip in ips:
                ips.remove(ip)
            # 重新请求url
            get_url(url, code=0, ips=[])
        else:
            date = datetime.datetime.now().strftime('%H:%M:%S')
            # result.text() 投票成功显示1  失败显示0
            print('第%s次 [%s] [%s]:投票%s (剩余可用代理IP数:%s)' %
                  (code, date, ip, result.text, len(ips)))
    
    
    def main():
        url = 'https://best.zhaopin.com/API/ScoreCompany.ashx'  # 投票的请求
        ips = []
        for i in range(6000):
            if i % 1000 == 0:
                ips.extend(get_ip())
                # print('-' * 100)
                # print(ips)
            t = threading.Thread(target=get_url, args=(url, i, ips))
            t.start()
            time.sleep(1)
    
    
    if __name__ == '__main__':
        main()
    

    Tesseract

    机器识别中的文字识别

    pip install pytesseract

    识别图片中的文字:

    #conding=utf-8
    
    import pytesseract
    from PIL import Image
    
    image = Image.open('./mayday.jpg')
    
    text = pytesseract.image_to_string(image)
    
    print(text)

    asyncio & aiohttp

    通过异步库aiohttp,asyncio爬取图片

    # -*- coding:utf-8 -*-
    import asyncio
    import os
    import time
    
    import aiohttp
    import requests
    
    
    class Spider(object):
        def __init__(self):
            self.num = 1
            if 'load-img' not in os.listdir('.'):
                os.mkdir('load-img')
            self.path = os.path.join(os.path.abspath('.'), 'load-img')
            os.chdir(self.path)  # 进入文件下载路径
    
        def run(self):
            start = time.time()
            for x in range(1, 101): # 爬取100张图片,更改数值,爬取更多图片
                links = self.__get_img_links(x)
                tasks = [asyncio.ensure_future(self.__download_img(
                    (link['id'], link['links']['download'])
                )) for link in links]
                loop = asyncio.get_event_loop()
                loop.run_until_complete(asyncio.wait(tasks))
                # if self.num >= 10:
                #     break
            end = time.time()
            print('run %s s' % (end - start))
    
        def __get_img_links(self, page):
            url = 'https://unsplash.com/napi/photos'
            data = {
                'page': page,
                'per_page': 12,
                'order_by': 'latest'
            }
            response = requests.get(url, params=data)
            if response.status_code == 200:
                return response.json()
            else:
                print('request %s' % response.status_code)
    
        async def __download_img(self, img):
            content = await self.__get_content(img[1])
            with open(img[0] + '.jpg', 'wb') as f:
                f.write(content)
            print('load %s page success' % self.num)
            self.num += 1
    
        async def __get_content(self, link):
            async with aiohttp.ClientSession() as session:
                response = await session.get(link)
                content = await response.read()
                return content
    
    
    def main():
        spider = Spider()
        spider.run()
    
    
    if __name__ == '__main__':
        main()
    

关键字