• 爬虫笔记五


    1. 会使用BeatifulSoup4解析和提取HTML/XML 数据
    2. Python类型和JSON类型的相互转化
    3. JsonPath解析和提取JSON文档
    4. 使用队列进行多线程爬虫
    5. 使用Selenium和PhantomJS爬取动态页面信息

    BeautifulSoup4解析器

    CSS 选择器:BeautifulSoup4

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

    lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。

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

    Beautiful Soup 3 目前已经停止开发,推荐现在的项目使用Beautiful Soup 4。

    pip install beautifulsoup4

    官方文档:http://beautifulsoup.readthedocs.io/zh_CN/v4.4.0

    抓取工具

    速度

    使用难度

    安装难度

    正则

    最快

    困难

    无(内置)

    BeautifulSoup

    最简单

    简单

    lxml

    简单

    一般

    BeautifulSoup4使用:

    # beautifulsoup4_test.py
    # encoding=utf-8
    
    from bs4 import BeautifulSoup
    
    html = """
    <html><head><title>The Dormouse's story</title></head>
    <body>
    <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
    <p class="story">Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
    and they lived at the bottom of a well.</p>
    <p class="story">...</p>
    """
    
    #创建 Beautiful Soup 对象
    soup = BeautifulSoup(html)
    
    #打开本地 HTML 文件的方式来创建对象
    #soup = BeautifulSoup(open('index.html'))
    
    #格式化输出 soup 对象的内容
    print soup.prettify()
    
    # 获取title标签
    print(soup.title)
    # <title>The Dormouse's story</title>
    
    
    # 获取title标签名称
    print(soup.title.name)
    # title
    
    
    # 获取title标签的内容
    print(soup.title.string)
    # The Dormouse's story
    
    
    # 获取title的父标签
    print(soup.title.parent)
    # <head><title>The Dormouse's story</title></head>
    
    
    # 获取title的父标签名称
    print(soup.title.parent.name)
    # head
    
    
    # 获取p标签
    print(soup.p)
    # <p class="title"><b>The Dormouse's story</b></p>
    
    
    # 获取p标签class属性
    print(soup.p['class'])
    #  ['title']    #返回的是list
    
    
    # 获取所有的a标签
    print(soup.find_all('a'))
    # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
    
    
    # 获取id='link3'的标签
    print(soup.find(id="link3"))
    # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
    
    
    # 获取所有的a标签的链接
    for link in soup.find_all('a'):
        print(link.get('href'))
    
    #   http://example.com/elsie
    #   http://example.com/lacie
    #   http://example.com/tillie
    
    
    # 获取文档中所有文字内容
    print(soup.get_text())

    BeautifulSoup(html_doc,"html.parser")   优势:Python内置,执行速度适中,文档容错能力强 劣势:Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差

    soup = BeautifulSoup(html,"lxml") 指定lxml解析器。优势:速度快,文档容错能力强(C编写),推荐使用

    BeautifulSoup(html_doc,"html5lib")    优势:最好的容错性,已浏览器的方式解析文档,生成Html5格式的文档 劣势:速度慢,不依赖外部扩展

    四大对象种类

    BeautifulSoup将复杂HTML文档转换成一个复杂的属性结构,每个节点都是对象.

    所有对象分为4种类型:TagNavigabStringBeautifulSoupComment

    1.1Tag:对象与XML或HTML原生文档中的tag相同
    print(soup.title)
    # <title>The Dormouse's story</title>
    
    print(type(soup.title))
    # <class 'bs4.element.Tag'>

    Tag有2个重要的属性:name , attrs

    • name:tag的标签名称
    • attrs:tag的属性
    print soup.name
    # [document] #soup 对象本身比较特殊,它的 name 即为 [document]
    
    print soup.head.name
    # head #对于其他内部标签,输出的值便为标签本身的名称
    
    print soup.p.attrs
    # {'class': ['title'], 'name': 'dromouse'}
    # 在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。
    
    print soup.p['class'] # soup.p.get('class')
    # ['title'] #还可以利用get方法,传入属性的名称,二者是等价的
    
    soup.p['class'] = "newClass"
    print soup.p # 可以对这些属性和内容等等进行修改
    # <p class="newClass" name="dromouse"><b>The Dormouse's story</b></p>
    
    del soup.p['class'] # 还可以对这个属性进行删除
    print soup.p
    # <p name="dromouse"><b>The Dormouse's story</b></p>
    1.2 NatigabString标签的文本内容
    print soup.p.string
    # The Dormouse's story
    
    print type(soup.p.string)
    # In [13]: <class 'bs4.element.NavigableString'>
    1.3 BeautifulSoup:表示一个文档内容,大部分时候,我们可以把它当做一个特殊的Tag
    print type(soup.name)
    # <type 'unicode'>
    
    print soup.name 
    # [document]
    
    print soup.attrs # 文档本身的属性为空
    # {}
    1.4 Comment:是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号
    makeup='<p><!--Hello--></p>'
    soup = BeatuifulSoup(makeup,'lxml')
    
    print(soup.p.string)    
    # Hello
    
    print(type(soup.p.string))
    # <class 'bs4.element.Comment'>

    遍历文档树

    2.1 子节点:.contents  .children属性

    tag的.contents属性可以将tag的子节点以列表的方式输出:

    # 输出方式为列表,我们可以用列表索引来获取它的某一个元素
    print(soup.p.contents[0])      # contents : 目录
    # [<b>The dormouse's story</b>]

    ret = soup.body.contents for i in ret: print i # <p class="title" name="dromouse"><b>The Dormouse's story</b></p> # # <p class="story"> # Once upon a time there were three little sisters; and their names were # <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; # and they lived at the bottom of a well. # </p> # # <p class="story">...</p>

    tag的.children返回一个  list  生成器,可以对tag的子节点进行循环。

    print soup.head.children
    #<listiterator object at 0x7f71457f5710>
    
    for child in  soup.body.children:
        print child
    2.2 所有子孙节点.descendants属性
    .descendants属性可以对所有的tag子孙节点进行递归循环,和.childern类似。
    for tag in soup.body.descendants:
        print(tag)
    
    # 输出结果:
    # <b>The Dormouse's story</b>
    # The Dormouse's story

    2.3节点内容: .string 属性

    如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点。如果一个tag仅有一个子节点,那么这个tag也可以使用 .string 方法,输出结果与当前唯一子节点的 .string 结果相同。

    通俗点说就是:如果一个标签里面没有标签了,那么 .string 就会返回标签里面的内容。

    如果标签里面只有唯一的一个标签了,那么 .string 也会返回最里面的内容。例如:

    print soup.head.string
    
    #The Dormouse's story
    
    print soup.title.string
    
    #The Dormouse's story

    搜索文档树

    1.find_all(name, attrs, recursive, text, **kwargs)

    name:查找名字为name的tag。(可以传入string,正则,列表)
    
    attrs:tag的属性
    
    recursive:是否递归,默认True
    
    text:tag标签文本
    
    limit:限制条数

    1name 参数

    name 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉

    A.传字符串

    最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的<b>标签:

    soup.find_all('b')
    
    soup.find_all('b')
    
    # [<b>The Dormouse's story</b>]
    
     
    
    print soup.find_all('a')
    
    #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

    B.传正则表达式

    如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容.下面例子中找出所有以b开头的标签,这表示<body><b>标签都应该被找到

    import re
    
    for tag in soup.find_all(re.compile("^b")):
    
        print(tag.name)
    
    # body
    
    # b

    C.传列表

    如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有<a>标签和<b>标签:

    soup.find_all(["a", "b"])
    
    # [<b>The Dormouse's story</b>,
    
    #  <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
    
    #  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
    
    #  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

    2)keyword 参数

    soup.find_all(id='link2')
    
    # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

    3)text 参数

    通过 text 参数可以搜搜文档中的字符串内容,与 name 参数的可选值一样, text 参数接受 字符串 , 正则表达式 , 列表

    soup.find_all(text="Elsie")
    
    # [u'Elsie']
    
     
    
    soup.find_all(text=["Tillie", "Elsie", "Lacie"])
    
    # [u'Elsie', u'Lacie', u'Tillie']
    
     
    
    soup.find_all(text=re.compile("Dormouse"))
    
    [u"The Dormouse's story", u"The Dormouse's story"]

    按CSS选择器搜索

    4.1 通过标签名查找

    print(soup.select('title'))
    [<title>The Dormouse's story</title>]

    7.2.2 通过类名查找

    print(soup.select('.sister'))
    # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

    7.2.3 通过id名查找

    print(soup.select("#link1"))
    # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

    7.2.4 组合查找

    print(soup.select('#link1,title'))
    # [<title>The Dormouse's story</title>, <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

    7.2.5 属性查找

    print(soup.select('a[class="sister"]'))
    # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, 
    # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

    7.2.6 获取内容

    for tag in soup.select('a'):
        print(tag.get_text())
    
    # Elsie
    # Lacie
    # Tillie

    案例:使用bs4的爬虫

    # bs4_tencent.py
    
    
    from bs4 import BeautifulSoup
    import urllib2
    import urllib
    import json    # 使用了json格式存储
    
    def tencent():
        url = 'http://hr.tencent.com/'
        request = urllib2.Request(url + 'position.php?&start=10#a')
        response =urllib2.urlopen(request)
        resHtml = response.read()
    
        output =open('tencent.json','w')
    
        html = BeautifulSoup(resHtml,'lxml')
    
    # 创建CSS选择器
        result = html.select('tr[class="even"]')
        result2 = html.select('tr[class="odd"]')
        result += result2
    
        items = []
        for site in result:
            item = {}
    
            name = site.select('td a')[0].get_text()
            detailLink = site.select('td a')[0].attrs['href']
            catalog = site.select('td')[1].get_text()
            recruitNumber = site.select('td')[2].get_text()
            workLocation = site.select('td')[3].get_text()
            publishTime = site.select('td')[4].get_text()
    
            item['name'] = name
            item['detailLink'] = url + detailLink
            item['catalog'] = catalog
            item['recruitNumber'] = recruitNumber
            item['publishTime'] = publishTime
    
            items.append(item)
    
        # 禁用ascii编码,按utf-8编码
        line = json.dumps(items,ensure_ascii=False)
    
        output.write(line.encode('utf-8'))
        output.close()
    
    if __name__ == "__main__":
       tencent()

     JSON模块与JsonPath

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互。

    JSON和XML的比较可谓不相上下。

    Python 2.7中自带了JSON模块,直接import json就可以使用了。

    官方文档:http://docs.python.org/library/json.html

    Json在线解析网站:http://www.json.cn/#

    json简单说就是javascript中的对象和数组,所以这两种结构就是对象和数组两种结构,通过这两种结构可以表示各种复杂的结构

    1. 对象:对象在js中表示为{ }括起来的内容,数据结构为 { key:value, key:value, ... }的键值对的结构,在面向对象的语言中,key为对象的属性,value为对应的属性值,所以很容易理解,取值方法为 对象.key 获取属性值,这个属性值的类型可以是数字、字符串、数组、对象这几种。
    2. 数组:数组在js中是中括号[ ]括起来的内容,数据结构为 ["Python", "javascript", "C++", ...],取值方式和所有语言中一样,使用索引获取,字段值的类型可以是 数字、字符串、数组、对象几种。

    import json

    json模块提供了四个功能:dumpsdumploads、loads,用于字符串 和 python数据类型间进行转换。

    名字 功能
    dumps python类型转化为json字符串
    dump

    将Python内置类型序列化为json对象后写入文件

    loads Json格式字符串解码转换成Python对象
    loads

    读取文件中json形式的字符串元素 转化成python类型

           json.loads()                     json.dumps()

    # json_loads.py
    
    import json
    
    strList = '[1, 2, 3, 4]'
    
    strDict = '{"city": "北京", "name": "大猫"}'
    
    json.loads(strList) 
    # [1, 2, 3, 4]
    
    json.loads(strDict) # json数据自动按Unicode存储
    # {u'city': u'u5317u4eac', u'name': u'u5927u732b'}
    # json_dumps.py
    
    import json
    import chardet
    
    listStr = [1, 2, 3, 4]
    tupleStr = (1, 2, 3, 4)
    dictStr = {"city": "北京", "name": "大猫"}
    
    json.dumps(listStr)
    # '[1, 2, 3, 4]'
    json.dumps(tupleStr)
    # '[1, 2, 3, 4]'
    
    # 注意:json.dumps() 序列化时默认使用的ascii编码
    # 添加参数 ensure_ascii=False 禁用ascii编码,按utf-8编码
    # chardet.detect()返回字典, 其中confidence是检测精确度
    
    json.dumps(dictStr) 
    # '{"city": "\u5317\u4eac", "name": "\u5927\u5218"}'
    
    chardet.detect(json.dumps(dictStr))
    # {'confidence': 1.0, 'encoding': 'ascii'}
    
    print json.dumps(dictStr, ensure_ascii=False) 
    # {"city": "北京", "name": "大刘"}
    
    chardet.detect(json.dumps(dictStr, ensure_ascii=False))
    # {'confidence': 0.99, 'encoding': 'utf-8'}
    # json_dump.py
    
    import json
    
    listStr = [{"city": "北京"}, {"name": "大刘"}]
    json.dump(listStr, open("listStr.json","w"), ensure_ascii=False)
    
    dictStr = {"city": "北京", "name": "大刘"}
    json.dump(dictStr, open("dictStr.json","w"), ensure_ascii=False)
    # json_load.py
    
    import json
    
    strList = json.load(open("listStr.json"))
    print strList
    
    # [{u'city': u'u5317u4eac'}, {u'name': u'u5927u5218'}]
    
    strDict = json.load(open("dictStr.json"))
    print strDict
    # {u'city': u'u5317u4eac', u'name': u'u5927u5218'}

    JsonPath

    JsonPath 是一种信息抽取类库,是从JSON文档中抽取指定信息的工具,提供多种语言实现版本,包括:Javascript, Python, PHP 和 Java。

    JsonPath 对于 JSON 来说,相当于 XPATH 对于 XML。

    下载地址:https://pypi.python.org/pypi/jsonpath

    安装方法:点击Download URL链接下载jsonpath,解压之后执行python setup.py install

    官方文档:http://goessner.net/articles/JsonPath

    JsonPath与XPath语法对比:

    Json结构清晰,可读性高,复杂度低,非常容易匹配,下表中对应了XPath的用法。

    XPath

    JSONPath

    描述

    /

    $

    根节点

    .

    @

    现行节点

    /

    .or[]

    取子节点

    ..

    n/a

    取父节点,Jsonpath未支持

    //

    ..

    就是不管位置,选择所有符合条件的条件

    *

    *

    匹配所有元素节点

    @

    n/a

    根据属性访问,Json不支持,因为Json是个Key-value递归结构,不需要。

    []

    []

    迭代器标示(可以在里边做简单的迭代操作,如数组下标,根据内容选值等)

    |

    [,]

    支持迭代器中做多选。

    []

    ?()

    支持过滤操作.

    n/a

    ()

    支持表达式计算

    ()

    n/a

    分组,JsonPath不支持



    # http://www.lagou.com/lbs/getAllCitySearchLabels.json 
    # jsonpath_lagou.py
    
    import urllib2
    import jsonpath
    import json
    import chardet
    
    url = 'http://www.lagou.com/lbs/getAllCitySearchLabels.json'
    request =urllib2.Request(url)
    response = urllib2.urlopen(request)
    html = response.read()
    
    # 把json格式字符串转换成python对象
    jsonobj = json.loads(html)
    
    # 从根节点开始,匹配name节点
    citylist = jsonpath.jsonpath(jsonobj,'$..name')
    
    print citylist
    print type(citylist)
    fp = open('city.json','w')
    
    content = json.dumps(citylist, ensure_ascii=False)
    print content
    
    fp.write(content.encode('utf-8'))
    fp.close()
    拉勾网城市JSON文件
    ##字符串编码转换
    
    这是中国程序员最苦逼的地方,什么乱码之类的几乎都是由汉字引起的。
    其实编码问题很好搞定,只要记住一点:
    
    ####任何平台的任何编码 都能和 Unicode 互相转换
    
    UTF-8 与 GBK 互相转换,那就先把UTF-8转换成Unicode,再从Unicode转换成GBK,反之同理。
    
    
    
    ``` python 
    # 这是一个 UTF-8 编码的字符串
    utf8Str = "你好地球"
    
    # 1. 将 UTF-8 编码的字符串 转换成 Unicode 编码
    unicodeStr = utf8Str.decode("UTF-8")
    
    # 2. 再将 Unicode 编码格式字符串 转换成 GBK 编码
    gbkData = unicodeStr.encode("GBK")
    
    # 1. 再将 GBK 编码格式字符串 转化成 Unicode
    unicodeStr = gbkData.decode("gbk")
    
    # 2. 再将 Unicode 编码格式字符串转换成 UTF-8
    utf8Str = unicodeStr.encode("UTF-8")
    实编码问题

    注意事项:

    decode的作用是将其他编码的字符串转换成 Unicode 编码

    encode的作用是将 Unicode 编码转换成其他编码的字符串

    一句话:UTF-8是对Unicode字符集进行编码的一种编码方式

    1.9.  多线程糗事百科爬虫案例

    案例要求参考上一个糗事百科单进程案例

    Queue(队列对象)

    Queue是python中的标准库,可以直接import Queue引用;队列是线程间最常用的交换数据的形式

    python下多线程的思考

    对于资源,加锁是个重要的环节。因为python原生的list,dict等,都是not thread safe的。而Queue,是线程安全的,因此在满足使用条件下,建议使用队列。

    1. 初始化: class Queue.Queue(maxsize) FIFO 先进先出
    2. 包中的常用方法:
    3.  Queue.qsize() 返回队列的大小
    4.  Queue.empty() 如果队列为空,返回True,反之False
    5.  Queue.full() 如果队列满了,返回True,反之False
    6.  Queue.full 与 maxsize 大小对应
    7.  Queue.get([block[, timeout]])获取队列,timeout等待时间
    8. 创建一个“队列”对象
    import Queue
    
    myqueue = Queue.Queue(maxsize = 10)
    
    将一个值放入队列中
    myqueue.put(10)
    
    将一个值从队列中取出
    myqueue.get()

    多线程示意图

    # encoding=utf-8
    
    import threading
    import requests
    import urllib3
    from Queue import Queue
    from lxml import etree
    import json
    import time
    
    
    # 抓取线程类
    class ThreadSpider(threading.Thread):
    
        def __init__(self, threadName, pageQueue, htmlQueue):
            # 继承父类初始化方法
            super(ThreadSpider, self).__init__()
            self.threadName = threadName
            self.pageQueue = pageQueue
            self.htmlQueue = htmlQueue
    
        def run(self):
            print "启动" + self.threadName
            while not self.pageQueue.empty():
                try:
                    # 从页码队列里面取出一个页码,先进先出
                    page = self.pageQueue.get(False)
                    url = "http://www.qiushibaike.com/8hr/page/" + str(page) + "/"
                    headers = {"User-Agent": "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"}
                    urllib3.disable_warnings()
                    response = requests.get(url, headers, verify=False)
                    html = response.text
                    self.htmlQueue.put(html)
                except:
                    pass
            print "结束" + self.threadName
    
    
    # 解析线程类
    class ThreadParser(threading.Thread):
    
        def __init__(self, threadName, htmlQueue, lock, file):
            super(ThreadParser, self).__init__()
            self.threadName = threadName
            self.htmlQueue = htmlQueue
            self.lock = lock
            self.file = file
    
        def run(self):
    
            print "启动" + self.threadName
            # print self.htmlQueue.empty()
            while not self.htmlQueue.empty():
                try:
                    html = self.htmlQueue.get(False)
                    self.parse(html)
                except:
                    pass
            print "结束" + self.threadName
    
        def parse(self, html):
            html = etree.HTML(html)
            result = html.xpath("//div[contains(@id, 'qiushi_tag_')]")
            items = []
            for tag in result:
                item = {}
                imgUrl = tag.xpath("./div[@class='author clearfix']//img/@src")[0]
                userName = tag.xpath("./div[@class='author clearfix']//img/@alt")[0]
    
                duanziContent = tag.xpath("./a/div[@class='content']/span/text()")[0].strip()
    
                vote = tag.xpath("./div[@class='stats']/span[1]/i/text()")[0]
    
                comments = tag.xpath("./div[@class='stats']/span[2]/a/i/text()")[0]
    
                item['imgUrl'] = imgUrl
                item['userName'] = userName
                item['duanziContent'] = duanziContent
                item['vote'] = vote
                item['comments'] = comments
    
                items.append(item)
    
            lines = json.dumps(items, ensure_ascii=False)
    
            content = lines.encode('utf-8') + "
    "
            print content
            self.lock.acquire()
            self.file.write(content)
            self.lock.release()
    
    
    def main():
        pageQueue = Queue(3)
        for i in range(1, 4):
            pageQueue.put(i)
    
        htmlQueue = Queue()
        file = open("douban.json", 'a')
        lock = threading.Lock()
    
        # 创建爬取进程
        spiderlThreadNameList = ["爬取线程1号", "爬取线程2号", "爬取线程3号"]
        spiderlThreadList = []
    
        for threadName in spiderlThreadNameList:
            thread = ThreadSpider(threadName, pageQueue, htmlQueue)
            thread.start()
            time.sleep(1)
            spiderlThreadList.append(thread)
    
        for thread in spiderlThreadList:
            thread.join()
    
        # 创建解析进程
        parserThreadNameList = ["解析线程1号", "解析线程2号", "解析线程3号"]
        parserThreadList = []
        for threadName in parserThreadNameList:
            thread = ThreadParser(threadName, htmlQueue, lock, file)
            thread.start()
            time.sleep(1)
            parserThreadList.append(thread)
    
        for thread in parserThreadList:
            thread.join()
    
        file.close()
    
    
    if __name__ == '__main__':
        main()
    
    

    动态HTML介绍

    JavaScript

    JavaScript 是网络上最常用也是支持者最多的客户端脚本语言。它可以收集 用户的跟踪数据,不需要重载页面直接提交表单,在页面嵌入多媒体文件,甚至运行网页游戏。

    我们可以在网页源代码的<scripy>标签里看到,比如:

    <script type="text/javascript" src="https://statics.huxiu.com/w/mini/static_2015/js/sea.js?v=201601150944"></script>

    jQuery

    jQuery 是一个十分常见的库,70% 最流行的网站(约 200 万)和约 30% 的其他网站(约 2 亿)都在使用。一个网站使用 jQuery 的特征,就是源代码里包含了 jQuery 入口,比如:

    <script type="text/javascript" src="https://statics.huxiu.com/w/mini/static_2015/js/jquery-1.11.1.min.js?v=201512181512"></script>

    如果你在一个网站上看到了 jQuery,那么采集这个网站数据的时候要格外小心。jQuery 可 以动态地创建 HTML 内容,只有在 JavaScript 代码执行之后才会显示。如果你用传统的方 法采集页面内容,就只能获得 JavaScript 代码执行之前页面上的内容。

    Ajax

    我们与网站服务器通信的唯一方式,就是发出 HTTP 请求获取新页面。如果提交表单之后,或从服务器获取信息之后,网站的页面不需要重新刷新,那么你访问的网站就在

    用Ajax 技术。

    Ajax 其实并不是一门语言,而是用来完成网络任务(可以认为 它与网络数据采集差不多)的一系列技术。Ajax 全称是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML),网站不需要使用单独的页面请求就可以和网络服务器进行交互 (收发信息)。

    DHTML

    Ajax 一样,动态 HTML(Dynamic HTML, DHTML)也是一系列用于解决网络问题的 技术集合。DHTML 是用客户端语言改变页面的 HTML 元素(HTML、CSS,或者二者皆 被改变)。比如页面上的按钮只有当用户移动鼠标之后才出现,背景色可能每次点击都会改变,或者用一个 Ajax 请求触发页面加载一段新内容,网页是否属于DHTML,关键要看有没有用 JavaScript 控制 HTML 和 CSS 元素。

    那么,如何搞定?

    那些使用了 Ajax 或 DHTML 技术改变 / 加载内容的页面,可能有一些采集手段。但是用 Python 解决这个问题只有两种途径:

    • 直接从 JavaScript 代码里采集内容(费时费力)
    • 用 Python 的 第三方库运行 JavaScript,直接采集你在浏览器里看到的页面(这个可以有)。

    Selenium与PhantomJS

    Selenium

    Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,类型像我们玩游戏用的按键精灵,可以按指定的命令自动操作,不同是Selenium 可以直接运行在浏览器上,它支持所有主流的浏览器(包括PhantomJS这些无界面的浏览器)。

    Selenium 可以根据我们的指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏,或者判断网站上某些动作是否发生。

    Selenium 自己不带浏览器,不支持浏览器的功能,它需要与第三方浏览器结合在一起才能使用。但是我们有时候需要让它内嵌在代码中运行,所以我们可以用一个叫 PhantomJS 的工具代替真实的浏览器。

    可以从 PyPI 网站下载 Selenium库https://pypi.python.org/simple/selenium ,也可以用 第三方管理器 pip用命令安装:pip install selenium

    Selenium 官方参考文档:http://selenium-python.readthedocs.io/index.html

    PhantomJS

    PhantomJS 是一个基于Webkit的“无界面”(headless)浏览器,它会把网站加载到内存并执行页面上的 JavaScript,因为不会展示图形界面,所以运行起来比完整的浏览器要高效。

    如果我们把 Selenium 和 PhantomJS 结合在一起,就可以运行一个非常强大的网络爬虫了,这个爬虫可以处理 JavaScrip、Cookie、headers,以及任何我们真实用户需要做的事情。

    注意:PhantomJS 只能从它的官方网站http://phantomjs.org/download.html) 下载。 因为 PhantomJS 是一个功能完善(虽然无界面)的浏览器而非一个 Python 库,所以它不需要像 Python 的其他库一样安装,但我们可以通过Selenium调用PhantomJS来直接使用。

    PhantomJS 官方参考文档:http://phantomjs.org/documentation

    快速入门

    Selenium 库里有个叫 WebDriver 的 API。WebDriver 有点儿像可以加载网站的浏览器,但是它也可以像 BeautifulSoup 或者其他 Selector 对象一样用来查找页面元素,与页面上的元素进行交互 (发送文本、点击等),以及执行其他动作来运行网络爬虫。

    # Python2 测试代码
    
     
    
    # 导入 webdriver
    
    from selenium import webdriver
    
     
    
    # 要想调用键盘按键操作需要引入keys包
    
    from selenium.webdriver.common.keys import Keys
    
     
    
    # 调用环境变量指定的PhantomJS浏览器创建浏览器对象
    
    driver = webdriver.PhantomJS()
    
     
    
    # 如果没有在环境变量指定PhantomJS位置
    
    # driver = webdriver.PhantomJS(executable_path="./phantomjs"))
    
     
    
    # get方法会一直等到页面被完全加载,然后才会继续程序,通常测试会在这里选择 time.sleep(2)
    
    driver.get("http://www.baidu.com/")
    
     
    
    # 获取页面名为 wrapper的id标签的文本内容
    
    data = driver.find_element_by_id("wrapper").text
    
     
    
    # 打印数据内容
    
    print data
    
     
    
    # 打印页面标题 "百度一下,你就知道"
    
    print driver.title
    
     
    
    # 生成当前页面快照并保存
    
    driver.save_screenshot("baidu.png")
    
     
    
    # id="kw"是百度搜索输入框,输入字符串"长城"
    
    driver.find_element_by_id("kw").send_keys(u"长城")
    
     
    
    # id="su"是百度搜索按钮,click() 是模拟点击
    
    driver.find_element_by_id("su").click()
    
     
    
    # 获取新的页面快照
    
    driver.save_screenshot("长城.png")
    
     
    
    # 打印网页渲染后的源代码
    
    print driver.page_source
    
     
    
    # 获取当前页面Cookie
    
    print driver.get_cookies()
    
     
    
    # ctrl+a 全选输入框内容
    
    driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'a')
    
     
    
    # ctrl+x 剪切输入框内容
    
    driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'x')
    
     
    
    # 输入框重新输入内容
    
    driver.find_element_by_id("kw").send_keys(u"极客营")
    
     
    
    # 模拟Enter回车键
    
    driver.find_element_by_id("su").send_keys(Keys.RETURN)
    
     
    
    # 清除输入框内容
    
    driver.find_element_by_id("kw").clear()
    
     
    
    # 生成新的页面快照
    
    driver.save_screenshot(u"极客营.png")
    
     
    
    # 获取当前url
    
    print driver.current_url
    
     
    
    # 关闭当前页面,如果只有一个页面,会关闭浏览器
    
    # driver.close()
    
     
    
    # 关闭浏览器
    
    driver.quit()
    快速入门

    页面操作

    Selenium 的 WebDriver提供了各种方法来寻找元素,假设下面有一个表单输入框

    <input type="text" name="user-name" id="passwd-id" />

    # 获取id标签值
    element = driver.find_element_by_id("passwd-id")
    # 获取name标签值
    element = driver.find_element_by_name("user-name")
    # 获取标签名值
    element = driver.find_elements_by_tag_name("input")
    # 也可以通过XPath来匹配
    element = driver.find_element_by_xpath("//input[@id='passwd-id']")
    页面操作

    定位UI元素 (WebElements)

    关于元素的选取,有如下的API 单个元素选取

    find_element_by_id
    
    find_elements_by_name
    
    find_elements_by_xpath
    
    find_elements_by_link_text
    
    find_elements_by_partial_link_text
    
    find_elements_by_tag_name
    
    find_elements_by_class_name
    
    find_elements_by_css_selector
    1. 1.    By ID

    <div id="coolestWidgetEvah">...</div>

    element = driver.find_element_by_id("coolestWidgetEvah")
    ------------------------ or -------------------------
    from selenium.webdriver.common.by import By
    element = driver.find_element(by=By.ID, value="coolestWidgetEvah")
    1. 2.    By Class Name
    <div class="cheese">
      <span>Cheddar</span>
    </div>
    <div class="cheese">
      <span>Gouda</span>
    </div>
    cheeses = driver.find_elements_by_class_name("cheese")
    ------------------------ or -------------------------
    from selenium.webdriver.common.by import By
    cheeses = driver.find_elements(By.CLASS_NAME, "cheese")
    1. 3.    By Tag Name
    <iframe src="..."></iframe>
    frame = driver.find_element_by_tag_name("iframe")
    ------------------------ or -------------------------
    from selenium.webdriver.common.by import By
    frame = driver.find_element(By.TAG_NAME, "iframe")
    1. 4.    By Name
    <input name="cheese" type="text"/>
    cheese = driver.find_element_by_name("cheese")
    ------------------------ or -------------------------
    from selenium.webdriver.common.by import By
    cheese = driver.find_element(By.NAME, "cheese")
    1. 5.    By Link Text
    <a href="http://www.google.com/search?q=cheese">cheese</a>
    cheese = driver.find_element_by_link_text("cheese")
    ------------------------ or -------------------------
    from selenium.webdriver.common.by import By
    cheese = driver.find_element(By.LINK_TEXT, "cheese")
    1. 6.    By Partial Link Text
    <a href="http://www.google.com/search?q=cheese">search for cheese</a>>
    cheese = driver.find_element_by_partial_link_text("cheese")
    ------------------------ or -------------------------
    from selenium.webdriver.common.by import By
    cheese = driver.find_element(By.PARTIAL_LINK_TEXT, "cheese")
    1. 7.    By CSS
    <div id="food"><span class="dairy">milk</span><span class="dairy aged">cheese</span></div>
    cheese = driver.find_element_by_css_selector("#food span.dairy.aged")
    ------------------------ or -------------------------
    from selenium.webdriver.common.by import By
    cheese = driver.find_element(By.CSS_SELECTOR, "#food span.dairy.aged")
    1. 8.    By XPath
    <input type="text" name="example" />
    <INPUT type="text" name="other" />
    inputs = driver.find_elements_by_xpath("//input")
    ------------------------ or -------------------------
    from selenium.webdriver.common.by import By
    inputs = driver.find_elements(By.XPATH, "//input")

    鼠标动作链

    有些时候,我们需要再页面上模拟一些鼠标操作,比如双击、右击、拖拽甚至按住不动等,我们可以通过导入 ActionChains 类来做到:

    #导入 ActionChains 类
    from selenium.webdriver import ActionChains
     
    # 鼠标移动到 ac 位置
    ac = driver.find_element_by_xpath('element')
    ActionChains(driver).move_to_element(ac).perform()
     
     
    # 在 ac 位置单击
    ac = driver.find_element_by_xpath("elementA")
    ActionChains(driver).move_to_element(ac).click(ac).perform()
     
    # 在 ac 位置双击
    ac = driver.find_element_by_xpath("elementB")
    ActionChains(driver).move_to_element(ac).double_click(ac).perform()
     
    # 在 ac 位置右击
    ac = driver.find_element_by_xpath("elementC")
    ActionChains(driver).move_to_element(ac).context_click(ac).perform()
     
    # 在 ac 位置左键单击hold住
    ac = driver.find_element_by_xpath('elementF')
    ActionChains(driver).move_to_element(ac).click_and_hold(ac).perform()
     
    # 将 ac1 拖拽到 ac2 位置
    ac1 = driver.find_element_by_xpath('elementD')
    ac2 = driver.find_element_by_xpath('elementE')
    ActionChains(driver).drag_and_drop(ac1, ac2).perform()

    填充表单

    我们已经知道了怎样向文本框中输入文字,但是有时候我们会碰到<select> </select>标签的下拉框。直接点击下拉框中的选项不一定可行。

    <select id="status" class="form-control valid" onchange="" name="status">
        <option value=""></option>
        <option value="0">未审核</option>
        <option value="1">初审通过</option>
        <option value="2">复审通过</option>
        <option value="3">审核不通过</option>
    </select>

    Selenium专门提供了Select类来处理下拉框。 其实 WebDriver 中提供了一个叫 Select 的方法,可以帮助我们完成这些事情:

    # 导入 Select 类
    from selenium.webdriver.support.ui import Select
     
    # 找到 name 的选项卡
    select = Select(driver.find_element_by_name('status'))
     
    select.select_by_index(1)   # 索引来选择
    select.select_by_value("0")  # 根据值来选择
    select.select_by_visible_text(u"未审核")  # 文字来选择

    以上是三种选择下拉框的方式,它可以根据索引来选择,可以根据值来选择,可以根据文字来选择。注意:

    •  index 索引从 0 开始
    •  value是option标签的一个属性值,并不是显示在下拉框中的值
    • visible_text是在option标签文本的值,是显示在下拉框的值

    全部取消选择怎么办呢?很简单:

    select.deselect_all()

    弹窗处理

    当你触发了某个事件之后,页面出现了弹窗提示,处理这个提示或者获取提示信息方法如下:

    alert = driver.switch_to_alert()

    页面切换

    # 一个浏览器肯定会有很多窗口,所以我们肯定要有方法来实现窗口的切换。# # 切换窗口的方法如下:
    driver.switch_to.window("this is window name")
    # 也可以使用 window_handles 方法来获取每个窗口的操作对象。例如: for handle in driver.window_handles: driver.switch_to_window(handle)

    页面前进和后退

    操作页面的前进和后退功能:

    driver.forward()     #前进
    driver.back()      # 后退

    Cookies

    # 获取页面每个Cookies值,用法如下
    
    for cookie in driver.get_cookies():
        print "%s -> %s" % (cookie['name'], cookie['value'])
    # 删除Cookies,用法如下
    
    # By name
    driver.delete_cookie("CookieName")
     
    # all
    driver.delete_all_cookies()

    页面等待

    注意:这是非常重要的一部分!!

    现在的网页越来越多采用了 Ajax 技术,这样程序便不能确定何时某个元素完全加载出来了。如果实际页面等待时间过长导致某个dom元素还没出来,但是你的代码直接使用了这个WebElement,那么就会抛出NullPointer的异常。

    为了避免这种元素定位困难而且会提高产生 ElementNotVisibleException 的概率。所以 Selenium 提供了两种等待方式,一种是隐式等待,一种是显式等待。

    隐式等待是等待特定的时间,显式等待是指定某一条件直到这个条件成立时继续执行。

    显式等待

    显式等待指定某个条件,然后设置最长等待时间。如果在这个时间还没有找到元素,那么便会抛出异常了。

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    # WebDriverWait 库,负责循环等待
    from selenium.webdriver.support.ui import WebDriverWait
    # expected_conditions 类,负责条件出发
    from selenium.webdriver.support import expected_conditions as EC
     
    driver = webdriver.Chrome()
    driver.get("http://www.xxxxx.com/loading")
    try:
        # 页面一直循环,直到 id="myDynamicElement" 出现
        element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.ID, "myDynamicElement"))
        )
    finally:
        driver.quit()

    如果不写参数,程序默认会 0.5s 调用一次来查看元素是否已经生成,如果本来元素就是存在的,那么会立即返回。

    下面是一些内置的等待条件,你可以直接调用这些条件,而不用自己写某些等待条件了。

    title_is
    title_contains
    presence_of_element_located
    visibility_of_element_located
    visibility_of
    presence_of_all_elements_located
    text_to_be_present_in_element
    text_to_be_present_in_element_value
    frame_to_be_available_and_switch_to_it
    invisibility_of_element_located
    element_to_be_clickable – it is Displayed and Enabled.
    staleness_of
    element_to_be_selected
    element_located_to_be_selected
    element_selection_state_to_be
    element_located_selection_state_to_be
    alert_is_present

    隐式等待

    隐式等待比较简单,就是简单地设置一个等待时间,单位为秒。

    from selenium import webdriver
     
    driver = webdriver.Chrome()
    driver.implicitly_wait(10) # seconds
    driver.get("http://www.xxxxx.com/loading")
    myDynamicElement = driver.find_element_by_id("myDynamicElement")

    当然如果不设置,默认等待时间为0。

    # douban.py
    
    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys
    import time
    
    driver = webdriver.PhantomJS()
    driver.get("http://www.douban.com")
    
    # 输入账号密码
    driver.find_element_by_name("form_email").send_keys("xxxxx@xxxx.com")
    driver.find_element_by_name("form_password").send_keys("xxxxxxxx")
    
    # 模拟点击登录
    driver.find_element_by_xpath("//input[@class='bn-submit']").click()
    
    # 等待3秒
    time.sleep(3)
    
    # 生成登陆后快照
    driver.save_screenshot("douban.png")
    
    with open("douban.html", "w") as file:
        file.write(driver.page_source)
    
    driver.quit()
    网站模拟登陆
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    # python的测试模块
    import unittest
    from selenium import webdriver
    from bs4 import BeautifulSoup
    
    
    class douyuSelenium(unittest.TestCase):
        # 初始化方法
        def setUp(self):
            self.driver = webdriver.PhantomJS()
    
        #具体的测试用例方法,一定要以test开头
        def testDouyu(self):
            self.driver.get('http://www.douyu.com/directory/all')
            while True:
                # 指定xml解析
                soup = BeautifulSoup(driver.page_source, 'lxml')
                # 返回当前页面所有房间标题列表 和 观众人数列表
                titles = soup.find_all('h3', {'class': 'ellipsis'})
                nums = soup.find_all('span', {'class': 'dy-num fr'})
    
                # 使用zip()函数来可以把列表合并,并创建一个元组对的列表[(1,2), (3,4)]
                for title, num in zip(nums, titles):
                    print u"观众人数:" + num.get_text().strip(), u"	房间标题: " + title.get_text().strip()
                # page_source.find()未找到内容则返回-1
                if driver.page_source.find('shark-pager-disable-next') != -1:
                    break
                # 模拟下一页点击
                self.driver.find_element_by_class_name('shark-pager-next').click()
    
        # 退出时的清理方法
        def tearDown(self):
            print '加载完成...'
            self.driver.quit()
    
    if __name__ == "__main__":
        unittest.main()
    动态网页模拟点击

    执行JavaScript语句

    from selenium import webdriver
    
    driver = webdriver.PhantomJS()
    driver.get("https://www.baidu.com/")
    
    # 给搜索输入框标红的javascript脚本
    js = "var q=document.getElementById("kw");q.style.border="2px solid red";"
    
    # 调用给搜索输入框标红js脚本
    driver.execute_script(js)
    
    #查看页面快照
    driver.save_screenshot("redbaidu.png")
    
    #js隐藏元素,将获取的图片元素隐藏
    img = driver.find_element_by_xpath("//*[@id='lg']/img")
    driver.execute_script('$(arguments[0]).fadeOut()',img)
    
    # 向下滚动到页面底部
    driver.execute_script("$('.scroll_top').click(function(){$('html,body').animate({scrollTop: '0px'}, 800);});")
    
    #查看页面快照
    driver.save_screenshot("nullbaidu.png")
    
    driver.quit()
    隐藏百度图片
    from selenium import webdriver
    import time
    
    driver = webdriver.PhantomJS()
    driver.get("https://movie.douban.com/typerank?type_name=剧情&type=11&interval_id=100:90&action=")
    
    # 向下滚动10000像素
    js = "document.body.scrollTop=10000"
    #js="var q=document.documentElement.scrollTop=10000"
    time.sleep(3)
    
    #查看页面快照
    driver.save_screenshot("douban.png")
    
    # 执行JS语句
    driver.execute_script(js)
    time.sleep(10)
    
    #查看页面快照
    driver.save_screenshot("newdouban.png")
    
    driver.quit()
    模拟滚动条滚动到底部

    selenium—test

    # encoding=utf-8
    
    import unittest  # python的测试模块
    from selenium_test1 import webdriver
    from selenium_test1.webdriver.common.keys import Keys
    import time
    from bs4 import BeautifulSoup
    
    
    class douyuSelenium(unittest.TestCase):
    
        # 初始化方法
        def setUp(self):
            self.driver = webdriver.Chrome()
    
        # 具体的测试用例方法,一定要以test开头
        def testDouyu(self):
            self.driver.get('http://www.douyu.com/directory/all')
            time.sleep(.5)
            page = self.driver.page_source  # selenium 的 page_source 可以获取到页面源码
            while True:
                soup = BeautifulSoup(page, 'lxml')  # 指定xml解析
                # 爬取 当前页面所有房间标题列表 和 观众人数列表
                titles = soup.find_all('h3', {'class': "ellipsis"})
                nums = soup.find_all('span', {'class': "dy-num fr"})
    
                # 使用zip()函数来可以把列表合并,并创建一个元组对的列表[(1,2), (3,4)]
                for title, num in zip(nums, titles):
                    print(u"观众人数:" + num.get_text().strip(), u"	房间标题: " + title.get_text().strip())
                # page_source.find()未找到内容则返回-1
                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):
            print('加载完成...')
    
            self.driver.quit()
    
    
    if __name__ == '__main__':
        unittest.main()
    登录百度




  • 相关阅读:
    Pytorch多进程最佳实践
    torchvision简介
    Pytorch数据变换(Transform)
    Pytorch自定义数据库
    DenseNet笔记
    Focal Loss笔记
    Pytorch数据读取框架
    二叉树的先序、中序、后序递归与非递归实现遍历
    Group Normalization笔记
    sap 怎么导出sap的各种表
  • 原文地址:https://www.cnblogs.com/Mint-diary/p/9681606.html
Copyright © 2020-2023  润新知