zoukankan      html  css  js  c++  java
  • Python网络爬虫概述(二)

    一、正则表达式

    正则表达式为我们提供了抓取数据的快捷方式。虽然该正则表达式更容易适应未来变化,但又存在难以构造、可读性差的问题。当在爬京东网的时候,正则表达式如下图所示:

    利用正则表达式实现对目标信息的精准采集

    此外 ,我们都知道,网页时常会产生变更,导致网页中会发生一些微小的布局变化时,此时也会使得之前写好的正则表达式无法满足需求,而且还不太好调试。当需要匹配的内容有很多的时候,使用正则表达式提取目标信息会导致程序运行的速度减慢,需要消耗更多内存。

    二、BeautifulSoup

    BeautifulSoup是一个非常流行的 Pyhon 模块。该模块可以解析网页,并提供定位内容的便捷接口。通过'pip install beautifulsoup4'就可以实现该模块的安装了。

    使用 BeautifulSoup的第一步是将己下载的 HTML 内容解析为 soup文档。

    由 于大多 数网 页都不具备良好的HTML 格式,因此BeautifulSoup需要对实际格式进行确定

    BeautifulSoup能够正确解析缺失的引号并闭合标签,此外还会添加<html >和<body>标签使其成为完整的HTML文档。

    通常使用find() 和find_all()方法来定位我们需要的元素。

    如果你想了解BeautifulSoup全部方法和参数,可以查阅BeautifulSoup的官方文档。虽然BeautifulSoup在代码的理解上比正则表达式要复杂一些,但是其更加容易构造和理解。

    三、Lxml

    Lxml模块使用 C语言编写,其解析速度比 BeautiflSoup更快,而且其安装过程也更为复杂,在此小编就不赘述啦。XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。

    Xpath

    使用 lxml 模块的第一步和BeautifulSoup一样,也是将有可能不合法的HTML 解析为 统一格式。

    虽然Lxml可以正确解析属性两侧缺失的引号,并闭合标签,不过该模块没有额外添加<html >和<body>标签

    在线复制Xpath表达式可以很方便的复制Xpath表达式。但是通过该方法得到的Xpath表达式放在程序中一般不能用,而且长的没法看。所以Xpath表达式一般还是要自己亲自上手。

    四、CSS

    CSS选择器表示选择元素所使用 的模式。BeautifulSoup整合了CSS选择器的语法和自身方便使用API。在网络爬虫的开发过程中,对于熟悉CSS选择器语法的人,使用CSS选择器是个非常方便的方法。

    CSS选择器

    下面是一些常用的选择器示例。

    • 选择所有标签: *
    • 选择<a>标 签: a
    • 选择所有class=”link” 的元素: .l in k
    • 选择 class=”link” 的<a>标签: a.link
    • 选择 id= " home ” 的<a>标签: a Jhome
    • 选择父元素为<a>标签的所有< span>子标签: a > span
    • 选择<a>标签内部的所有<span>标签: a span
    • 选择title属性为” Home ” 的所有<a>标签: a [title=Home]

    五、性能对比

    lxml 和正则表达式模块都是C语言编写的,而BeautifulSoup则是纯Python 编写的。下表总结了每种抓取方法的优缺点。

    相对困难需要注意的是。lxml在内部实现中,实际上是将CSS选择器转换为等价的Xpath选择器。

    六、总结

    如果你的爬虫瓶颈是下载网页,而不是抽取数据的话,那么使用较慢的方法(如BeautifulSoup) 也不成问题。

    如果只需抓取少量数据,并且想要避免额外依赖的话,那么正则表达式可能更加适合。

    不过,通常情况下,

    l xml是抓取数据的最好选择,这是因为该方法既快速又健壮,而正则表达式和BeautifulSoup只在某些特定场景下有用。

    Python爬虫谷歌Chrome F12抓包过程原理解析

    浏览器打开网页的过程就是爬虫获取数据的过程,两者是一样一样的。浏览器渲染的网页是丰富多彩的数据集合,而爬虫得到的是网页的源代码htm有时候,我们不能在网页的html代码里面找到想要的数据,但是浏览器打开的网页上面却有这些数据。这就是浏览器通过ajax技术异步加载(偷偷下载)了这些数据

    大家禁不住要问:那么该如何看到浏览器偷偷下载的那些数据呢?

    答案就是谷歌Chrome浏览器的F12快捷键,也可以通过鼠标右键菜单“检查”(Inspect)打开Chrome自带的开发者工具,开发者工具会出现在浏览器网页的左侧或者是下面(可调整),它的样子就是这样的:

    让我们简单了解一下它如何使用:

    谷歌Chrome抓包:1. 最上面一行菜单

    左上角箭头 用来点击查看网页的元素

    第二个手机、平板图标是用来模拟移动端显示网页

    Elements 查看渲染后的网页标签元素

    提醒 是渲染后(包括异步加载的图片、数据等)的完整网页的html,不是最初下载的那个html。

    Console 查看JavaScript的console log信息,写网页时比较有用

    Sources 显示网页源码、CSS、JavaScript代码

    Network 查看所有加载的请求,对爬虫很有帮助

    后面的暂且不管。

    谷歌Chrome抓包:2. 重要区域

    图中红框的两个按钮比较有用,编号为2的是清空请求记录;编号3的是保持记录,这在网页有重定向的时候很有用

    图中绿色区域就是加载完整个网页,浏览器的全部请求记录,包括网址、状态、类型等。写爬虫时,我们就要在这里寻找线索,提炼金矿

    最下面编号为4的红框显示了加载这个网页,一共请求了181次,数量是多么地惊人,让人不禁心疼七浏览器来。

    点击一条请求的网址,右侧就会出现新的窗口显示该条请求的相信信息:

    图中左边红框就是点击的请求网址;绿框就是详情窗口。

    详情窗口包括,Headers(请求头)、Preview(预览响应)、Response(服务器响应内容)和Timing(耗时)。

    Preview、Response 帮助我们查看该条请求是不是有爬虫想要的数据;

    Headers帮助我们在爬虫中重建http请求,以便爬虫得到和浏览器一样的数据。

    了解和熟练使用Chrome的开发者工具,大家就如虎添翼可以顺利写出自己的爬虫啦。

    Python爬虫JSON及JSONPath运行原理详解

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

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

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

    JsonPath与XPath语法对比:

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

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import urllib2 # json解析库,对应到lxml import json # json的解析语法,对应到xpath import jsonpath
    url
    = "http://www.lagou.com/lbs/getAllCitySearchLabels.json" headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36'}
    request
    = urllib2.Request(url, headers = headers) response = urllib2.urlopen(request)
    # 取出json文件里的内容,返回的格式是字符串 html = response.read() # 把json形式的字符串转换成python形式的Unicode字符串 unicodestr = json.loads(html)
    # Python形式的列表 city_list = jsonpath.jsonpath(unicodestr, "$..name") #for item in city_list: # print item # dumps()默认中文为ascii编码格式,ensure_ascii默认为Ture # 禁用ascii编码格式,返回的Unicode字符串,方便使用 array = json.dumps(city_list, ensure_ascii=False) #json.dumps(city_list) #array = json.dumps(city_list) with open("lagoucity.json", "w") as f: f.write(array.encode("utf-8"))
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import urllib2
    import json
    from lxml import etree
    url
    = "http://www.qiushibaike.com/8hr/page/2/" headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36'} request = urllib2.Request(url, headers = headers) html = urllib2.urlopen(request).read()
    # 响应返回的是字符串,解析为HTML DOM模式 text = etree.HTML(html) text = etree.HTML(html)

    # 返回所有段子的结点位置,contains()模糊查询方法,第一个参数是要匹配的标签,第二个参数是标签名部分内容 node_list = text.xpath('//div[contains(@id, "qiushi_tag")]')

    items
    ={} for node in node_list: # xpath返回的列表,这个列表就这一个参数,用索引方式取出来,用户名 username = node.xpath('./div/a/@title')[0]
    # 取出标签下的内容,段子内容 content = node.xpath('.//div[@class="content"]/span')[0].text
    # 取出标签里包含的内容,点赞 zan = node.xpath('.//i')[0].text
    # 评论 comments = node.xpath('.//i')[1].text

    items
    = { "username" : username, "content" : content, "zan" : zan, "comments" : comments } with open("qiushi.json", "a") as f: f.write(json.dumps(items, ensure_ascii=False).encode("utf-8") + " ")

    一、python中的str和unicode
    一直以来,python中的中文编码就是一个极为头大的问题,经常抛出编码转换的异常,python中的str和unicode到底是一个什么东西呢?
    在python中提到unicode,一般指的是unicode对象,例如'哈哈'的unicode对象为  u'u54c8u54c8'


    而str,是一个字节数组,这个字节数组表示的是对unicode对象编码(可以是utf-8、gbk、cp936、GB2312)后的存储的格式。

    这里它仅仅是一个字节流,没有其它的含义,如果你想使这个字节流显示的内容有意义,就必须用正确的编码格式,解码显示。
    例如:

    对于unicode对象哈哈进行编码,编码成一个utf-8编码的str-s_utf8,s_utf8就是是一个字节数组,存放的就是'xe5x93x88xe5x93x88',但是这仅仅是一个字节数组,如果你想将它通过print语句输出成哈哈,那你就失望了,为什么呢?

     

    因为print语句它的实现是将要输出的内容传送了操作系统,操作系统会根据系统的编码对输入的字节流进行编码,这就解释了为什么utf-8格式的字符串“哈哈”,输出的是“鍝堝搱”,因为 'xe5x93x88xe5x93x88'用GB2312去解释,其显示的出来就是“鍝堝搱”。这里再强调一下,str记录的是字节数组,只是某种编码的存储格式,至于输出到文件或是打印出来是什么格式,完全取决于其解码的编码将它解码成什么样子。

    这里再对print进行一点补充说明:当将一个unicode对象传给print时,在内部会将该unicode对象进行一次转换,转换成本地的默认编码(这仅是个人猜测)

    二、str和unicode对象的转换

    str和unicode对象的转换,通过encode和decode实现,具体使用如下:

    将GBK'哈哈'转换成unicode,然后再转换成UTF8

    三、Setdefaultencoding

    如上图的演示代码所示:

    当把s(gbk字符串)直接编码成utf-8的时候,将抛出异常,但是通过调用如下代码:

    import sys

    reload(sys)

    sys.setdefaultencoding('gbk')

    后就可以转换成功,为什么呢?在python中str和unicode在编码和解码过程中,如果将一个str直接编码成另一种编码,会先把str解码成unicode,采用的编码为默认编码,一般默认编码是anscii,所以在上面示例代码中第一次转换的时候会出错,当设定当前默认编码为'gbk'后,就不会出错了。

    至于reload(sys)是因为Python2.5 初始化后会删除 sys.setdefaultencoding 这个方法,我们需要重新载入。

    四、操作不同文件的编码格式的文件

    建立一个文件test.txt,文件格式用ANSI,内容为:

    abc中文

    用python来读取

    # coding=gbk

    print open("Test.txt").read()

    结果:abc中文

    把文件格式改成UTF-8:

    结果:abc涓�枃

    显然,这里需要解码:

    # coding=gbk

    import codecs

    print open("Test.txt").read().decode("utf-8")

    结果:abc中文

    上面的test.txt我是用Editplus来编辑的,但当我用Windows自带的记事本编辑并存成UTF-8格式时,

    运行时报错:

    Traceback (most recent call last):

    File "ChineseTest.py", line 3, in 

    print open("Test.txt").read().decode("utf-8")

    UnicodeEncodeError: 'gbk' codec can't encode character u'ufeff' in position 0: illegal multibyte sequence

    原来,某些软件,如notepad,在保存一个以UTF-8编码的文件时,会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF,即BOM)。

    因此我们在读取时需要自己去掉这些字符,python中的codecs module定义了这个常量:

    # coding=gbk

    import codecs

    data = open("Test.txt").read()

    if data[:3] == codecs.BOM_UTF8:

    data = data[3:]

    print data.decode("utf-8")

    结果:abc中文

    五、文件的编码格式和编码声明的作用

    源文件的编码格式对字符串的声明有什么作用呢?这个问题困扰一直困扰了我好久,现在终于有点眉目了,文件的编码格式决定了在该源文件中声明的字符串的编码格式,例如:

    str = '哈哈'

    print repr(str)

    a.如果文件格式为utf-8,则str的值为:'xe5x93x88xe5x93x88'(哈哈的utf-8编码)

    b.如果文件格式为gbk,则str的值为:'xb9xfexb9xfe'(哈哈的gbk编码)

    在第一节已经说过,python中的字符串,只是一个字节数组,所以当把a情况的str输出到gbk编码的控制台时,就将显示为乱码:鍝堝搱;而当把b情况下的str输出utf-8编码的控制台时,也将显示乱码的问题,是什么也没有,也许'xb9xfexb9xfe'用utf-8解码显示,就是空白吧。>_<

    说完文件格式,现在来谈谈编码声明的作用吧,每个文件在最上面的地方,都会用# coding=gbk 类似的语句声明一下编码,但是这个声明到底有什么用呢?到止前为止,我觉得它的作用也就是三个:

    1. 声明源文件中将出现非ascii编码,通常也就是中文;
    2. 在高级的IDE中,IDE会将你的文件格式保存成你指定编码格式。
    3. 决定源码中类似于u'哈'这类声明的将‘哈'解码成unicode所用的编码格式,也是一个比较容易让人迷惑的地方,看示例:

    #coding:gbk

    ss = u'哈哈'

    print repr(ss)

    print 'ss:%s' % ss

    将这个些代码保存成一个utf-8文本,运行,你认为会输出什么呢?大家第一感觉肯定输出的肯定是:

    u'u54c8u54c8'

    ss:哈哈

    但是实际上输出是:

    u'u935du581du6431'

    ss:鍝堝搱

    为什么会这样,这时候,就是编码声明在作怪了,在运行ss = u'哈哈'的时候,整个过程可以分为以下几步:

    1) 获取'哈哈'的编码:由文件编码格式确定,为'xe5x93x88xe5x93x88'(哈哈的utf-8编码形式)

    2) 转成 unicode编码的时候,在这个转换的过程中,对于'xe5x93x88xe5x93x88'的解码,不是用utf-8解码,而是用声明编码处指定的编码GBK,将'xe5x93x88xe5x93x88'按GBK解码,得到就是''鍝堝搱'',这三个字的unicode编码就是u'u935du581du6431',至止可以解释为什么print repr(ss)输出的是u'u935du581du6431' 了。

    好了,这里有点绕,我们来分析下一个示例:

    #-*- coding:utf-8 -*-

    ss = u'哈哈'

    print repr(ss)

    print 'ss:%s' % ss

    将这个示例这次保存成GBK编码形式,运行结果,竟然是:

    UnicodeDecodeError: 'utf8' codec can't decode byte 0xb9 in position 0: unexpected code byte

    这里为什么会有utf8解码错误呢?想想上个示例也明白了,转换第一步,因为文件编码是GBK,得到的是'哈哈'编码是GBK的编码'xb9xfexb9xfe',当进行第二步,转换成 unicode的时候,会用UTF8对'xb9xfexb9xfe'进行解码,而大家查utf-8的编码表会发现,utf8编码表(关于UTF- 8解释可参见字符编码笔记:ASCII、UTF-8、UNICODE)中根本不存在,所以会报上述错误。

  • 相关阅读:
    [模板] 主席树
    [模板] 替罪羊树
    [模板] Treap
    [LUOGU] P4342 [IOI1998]Polygon
    [JOYOI] 1051 选课
    poj 1845 数论(唯一分解定理+分治法求等比数列前n项的和mod m的值)
    poj 2418 bst统计字符串
    hdu 3791 二叉排序树
    hdu 3999 二叉排序树
    toj 3711 水题
  • 原文地址:https://www.cnblogs.com/chengxuyonghu/p/13705316.html
Copyright © 2011-2022 走看看