python 爬虫之路教程

原址

摘要: From:https://piaosanlang.gitbooks.io/spiders/content/ 爬虫入门初级篇 IDE 选择: PyCharm(推荐)、SublimeText3、VS2015、wingIDE 装python2还是python3 ? python社区需要很多年才能将现有的模块移植到支持python3. django web.py flask等还不支持p

Fromhttps://piaosanlang.gitbooks.io/spiders/content/

爬虫入门初级篇

IDE 选择 PyCharm(推荐)、SublimeText3、VS2015、wingIDE
装python2还是python3 
python社区需要很多年才能将现有的模块移植到支持python3. django web.py flask等还不支持python3。所以推荐安装python2 最新版。
Windows 平台
从 http://python.org/download/ 上安装Python 2.7。您需要修改 PATH 环境变量将Python的可执行程序及额外的脚本添加到系统路径中。将以下路径添加到 PATH 中:
C:\Python2.7\;C:\Python2.7\Scripts\; 。从 http://sourceforge.net/projects/pywin32/ 安装 pywin32。 请确认下载符合您系统的版本(win32或者amd64)
Linux Ubuntu 平台
安装Python sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev

学习需求抓取的某个网站或者某个应用的内容提取有用的价值
实现手段模拟用户在浏览器或者应用(app)上的操作实现自动化的程序爬虫应用场景利用爬虫能做什么
大家最熟悉的应用场景抢票神器360抢票器、投票神器微信朋友圈投票

360抢票器

投票神器

投票神器

企业应用场景
  • 拉勾网招聘职位数据分析报告
  • 2016年中国外卖O2O行业发展报告
  • 2016年中国在线出境游市场研究报告

1、各种热门公司招聘中的职位数及月薪分布

职位

2、对某个App的下载量跟踪

3、 饮食地图

还可以把男的排除掉只看女的

4、 票房预测

爬虫是什么
专业术语 网络爬虫又被称为网页蜘蛛网络机器人网络爬虫是一种按照一定的规则自动的抓取万维网信息的程序或者脚本。
爬虫起源产生背景
随着网络的迅速发展万维网成为大量信息的载体如何有效地提取并利用这些信息成为一个巨大的挑战搜索引擎有YahooGoogle百度等,作为一个辅助人们检索信息的工具成为用户访问万维网的入口和指南。网络爬虫是搜索引擎系统中十分重要的组成部分它负责从互联网中搜集网页采集信息这些网页信息用于建立索引从而为搜索 引擎提供支持它决定着整个引擎系统的内容是否丰富信息是否即时因此其性能的优劣直接影响着搜索引擎的效果。



网络爬虫程序的优劣很大程度上反映了一个搜索引擎的好差。
不信你可以随便拿一个网站去查询一下各家搜索对它的网页收录情况爬虫强大程度跟搜索引擎好坏基本成正比。

搜索引擎工作原理

第一步抓取网页爬虫

搜索引擎是通过一种特定规律的软件跟踪网页的链接从一个链接爬到另外一个链接像蜘蛛在蜘蛛网上爬行一样所以被称为“蜘蛛”也被称为“机器人”。搜索引擎蜘蛛的爬行是被输入了一定的规则的它需要遵从一些命令或文件的内容。       Robots协议也称为爬虫协议、机器人协议等的全称是“网络爬虫排除标准”Robots Exclusion Protocol网站通过Robots协议告诉搜索引擎哪些页面可以抓取哪些页面不能抓取
https://www.taobao.com/robots.txt
http://www.qq.com/robots.txt
https://www.taobao.com/robots.txt

第二步数据存储

搜索引擎是通过蜘蛛跟踪链接爬行到网页并将爬行的数据存入原始页面数据库。其中的页面数据与用户浏览器得到的HTML是完全一样的。搜索引擎蜘蛛在抓取页面时也做一定的重复内容检测一旦遇到权重很低的网站上有大量抄袭、采集或者复制的内容很可能就不再爬行。

第三步预处理

搜索引擎将蜘蛛抓取回来的页面进行各种步骤的预处理。
⒈提取文字 ⒉中文分词 ⒊去停止词 ⒋消除噪音搜索引擎需要识别并消除这些噪声比如版权声明文字、导航条、广告等…… 5.正向索引 6.倒排索引 7.链接关系计算 8.特殊文件处理。 除了HTML文件外搜索引擎通常还能抓取和索引以文字为基础的多种文件类型如 PDF、Word、WPS、XLS、PPT、TXT 文件等。我们在搜索结果中也经常会看到这些文件类型。但搜索引擎还不能处理图片、视频、Flash 这类非文字内容也不能执行脚本和程序。

第四步排名提供检索服务

但是这些通用性搜索引擎也存在着一定的局限性如
(1)不同领域、不同背景的用户往往具有不同的检索目的和需求通用搜索引擎所返回的结果包含大量用户不关心的网页。
(2)通用搜索引擎的目标是尽可能大的网络覆盖率有限的搜索引擎服务器资源与无限的网络数据资源之间的矛盾将进一步加深。
(3)万维网数据形式的丰富和网络技术的不断发展图片、数据库、音频、视频多媒体等不同数据大量出现通用搜索引擎往往对这些信息含量密集且具有一定结构的数据无能为力不能很好地发现和获取。
(4)通用搜索引擎大多提供基于关键字的检索难以支持根据语义信息提出的查询。
为了解决上述问题定向抓取相关网页资源的聚焦爬虫应运而生。 聚焦爬虫是一个自动下载网页的程序它根据既定的抓取目标有选择的访问万维网上的网页与相关的链接获取所需要的信息。
与通用爬虫(general purpose web crawler)不同聚焦爬虫并不追求大的覆盖而将目标定为抓取与某一特定主题内容相关的网页为面向主题的用户查询准备数据资源。
聚焦爬虫工作原理以及关键技术概述
网络爬虫是一个自动提取网页的程序它为搜索引擎从万维网上下载网页是搜索引擎的重要组成。传统爬虫从一个或若干初始网页的URL开始获得初始网页上的URL在抓取网页的过程中不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。聚焦爬虫的工作流程较为复杂需要根据一定的网页分析算法过滤与主题无关的链接保留有用的链接并将其放入等待抓取的URL队列。然后它将根据一定的搜索策略从队列中选择下一步要抓取的网页URL并重复上述过程直到达到系统的某一条件时停止。另外所有被爬虫抓取的网页将会被系统存贮进行一定的分析、过滤并建立索引以便之后的查询和检索对于聚焦爬虫来说这一过程所得到的分析结果还可能对以后的抓取过程给出反馈和指导。
相对于通用网络爬虫聚焦爬虫还需要解决三个主要问题
(1) 对抓取目标的描述或定义
(2) 对网页或数据的分析与过滤
(3) 对URL的搜索策略。
抓取目标的描述和定义是决定网页分析算法与URL搜索策略如何制订的基础。而网页分析算法和候选URL排序算法是决定搜索引擎所提供的服务形式和爬虫网页抓取行为的关键所在。这两个部分的算法又是紧密相关的。

网络爬虫的发展趋势

随着AJAX/Web2.0的流行如何抓取AJAX等动态页面成了搜索引擎急需解决的问题如果搜索引擎依旧采用“爬”的机制是无法抓取到AJAX页面的有效数据的。 对于AJAX这样的技术所需要的爬虫引擎必须是基于驱动的。而如果想要实现事件驱动首先需要解决以下问题
第一JavaScript的交互分析和解释
第二DOM事件的处理和解释分发
第三动态DOM内容语义的抽取。

爬虫发展的几个阶段博士论文copy

第一个阶段可以说是早期爬虫斯坦福的几位同学完成的抓取当时的互联网基本都是完全开放的人类流量是主流
第二个阶段是分布式爬虫但是爬虫面对新的问题是数据量越来越大传统爬虫已经解决不了把数据都抓全的问题需要更多的爬虫于是调度问题就出现了
第三阶段是暗网爬虫。此时面对新的问题是数据之间的link越来越少比如淘宝点评这类数据彼此link很少那么抓全这些数据就很难还有一些数据是需要提交查询词才能获取比如机票查询那么需要寻找一些手段“发现”更多更完整的不是明面上的数据。
第四阶段智能爬虫这主要是爬虫又开始面对新的问题社交网络数据的抓取。

社交网络对爬虫带来的新的挑战包括
有一条账号护城河
我们通常称UGCUser Generated Content指用户原创内容。为web2.0即数据从单向传达到双向互动人民群众可以与网站产生交互因此产生了账号每个人都通过账号来标识身份提交数据这样一来社交网络就可以通过封账号来提高数据抓取的难度通过账号来发现非人类流量。之前没有账号只能通过cookie和ip。cookie又是易变易挥发的很难长期标识一个用户。

网络走向封闭
新浪微博在2012年以前都是基本不封的随便写一个程序怎么抓都不封但是很快越来越多的站点都开始防止竞争对手防止爬虫来抓取数据逐渐走向封闭越来越多的人难以获得数据。甚至都出现了专业的爬虫公司这在2010年以前是不可想象的。。

反爬手段封杀手法千差万别
写一个通用的框架抓取成百上千万的网站已经成为历史或者说已经是一个技术相对成熟的工作也就是已经有相对成熟的框架来”盗“成百上千的墓但是极个别的墓则需要特殊手段了目前市场上比较难以抓取的数据包括微信公共账号微博facebookins淘宝等等。具体原因各异但基本无法用一个统一框架来完成太特殊了。如果有一个通用的框架能解决我说的这几个网站的抓取这一定是一个非常震撼的产品如果有一定要告诉我那我公开出来然后就改行了。
当面对以上三个挑战的时候就需要智能爬虫。智能爬虫是让爬虫的行为尽可能模仿人类行为让反爬策略失效只有”混在老百姓队伍里面才是安全的“因此这就需要琢磨浏览器了很多人把爬虫写在了浏览器插件里面把爬虫写在了手机里面写在了路由器里面(春节抢票王)。再有一个传统的爬虫都是只有读操作的没有写操作这个很容易被判是爬虫智能的爬虫需要有一些自动化交互的行为这都是一些抵御反爬策略的方法。+

从商业价值上是一个能够抽象千百万网站抓取框架的爬虫工程师值钱还是一个能抓特定难抓网站的爬虫工程师值钱
能花钱来买被市场认可的数据都是那些特别难抓的抓取成本异常高的数据。
目前市场上主流的爬虫工程师都是能够抓成百上千网站的数据但如果想有价值还是得有能力抓特别难抓的数据才能估上好价钱。

爬虫基本原理

爬虫是 模拟用户在浏览器或者某个应用上的操作把操作的过程、实现自动化的程序
当我们在浏览器中输入一个url后回车后台会发生什么比如说你输入http://www.sina.com.cn/
简单来说这段过程发生了以下四个步骤
1.查找域名对应的IP地址。
2.向IP对应的服务器发送请求。
3.服务器响应请求发回网页内容。
4.浏览器解析网页内容。

浏览器工作原理

网络爬虫本质本质就是浏览器http请求。
浏览器和网络爬虫是两种不同的网络客户端都以相同的方式来获取网页
网络爬虫要做的简单来说就是实现浏览器的功能。通过指定url直接返回给用户所需要的数据 而不需要一步步人工去操纵浏览器获取。

浏览器是如何发送和接收这个数据呢 HTTP简介。
HTTP协议HyperText Transfer Protocol超文本传输协议目的是为了提供一种发布和接收HTML(HyperText Markup Language)页面的方法。
HTTP协议所在的协议层了解
HTTP是基于TCP协议之上的。在TCP/IP协议参考模型的各层对应的协议如下图,其中HTTP是应用层的协议。 默认HTTP的端口号为80HTTPS的端口号为443。


HTTP工作过程
一次HTTP操作称为一个事务其工作整个过程如下
1 ) 、地址解析
如用客户端浏览器请求这个页面http://localhost.com:8080/index.htm从中分解出协议名、主机名、端口、对象路径等部分对于我们的这个地址解析得到的结果如下 协议名http 主机名localhost.com 端口8080 对象路径/index.htm在这一步需要域名系统DNS解析域名localhost.com,得主机的IP地址。
2、封装HTTP请求数据包
把以上部分结合本机自己的信息封装成一个HTTP请求数据包
3封装成TCP包建立TCP连接TCP的三次握手
在HTTP工作开始之前客户机Web浏览器首先要通过网络与服务器建立连接该连接是通过TCP来完成的该协议与IP协议共同构建Internet即著名的TCP/IP协议族因此Internet又被称作是TCP/IP网络。HTTP是比TCP更高层次的应用层协议根据规则只有低层协议建立之后才能才能进行更层协议的连接因此首先要建立TCP连接一般TCP连接的端口号是80。这里是8080端口
4客户机发送请求命令
建立连接后客户机发送一个请求给服务器请求方式的格式为统一资源标识符URL、协议版本号后边是MIME信息包括请求修饰符、客户机信息和可内容。
5服务器响应
服务器接到请求后给予相应的响应信息其格式为一个状态行包括信息的协议版本号、一个成功或错误的代码后边是MIME信息包括服务器信息、实体信息和可能的内容。
实体消息是服务器向浏览器发送头信息后它会发送一个空白行来表示头信息的发送到此为结束接着它就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据
6服务器关闭TCP连接
一般情况下一旦Web服务器向浏览器发送了请求数据它就要关闭TCP连接然后如果浏览器或者服务器在其头信息加入了这行代码Connection:keep-alive
TCP连接在发送后将仍然保持打开状态于是浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间还节约了网络带宽。

HTTP协议栈数据流


HTTPS

HTTPS全称Hypertext Transfer Protocol over Secure Socket Layer是以安全为目标的HTTP通道简单讲是HTTP的安全版。即HTTP下加入SSL层HTTPS的安全基础是SSL。其所用的端口号是443。
SSL安全套接层是netscape公司设计的主要用于web的安全传输协议。这种协议在WEB上获得了广泛的应用。通过证书认证来确保客户端和网站服务器之间的通信数据是加密安全的。
有两种基本的加解密算法类型
1对称加密symmetrcic encryption密钥只有一个加密解密为同一个密码且加解密速度快典型的对称加密算法有DES、AESRC53DES等对称加密主要问题是共享秘钥除你的计算机客户端知道另外一台计算机服务器的私钥秘钥否则无法对通信流进行加密解密。解决这个问题的方案非对称秘钥。
2非对称加密使用两个秘钥公共秘钥和私有秘钥。私有秘钥由一方密码保存一般是服务器保存另一方任何人都可以获得公共秘钥。这种密钥成对出现且根据公钥无法推知私钥根据私钥也无法推知公钥加密解密使用不同密钥公钥加密需要私钥解密私钥加密需要公钥解密相对对称加密速度较慢典型的非对称加密算法有RSA、DSA等。

https通信的优点
客户端产生的密钥只有客户端和服务器端能得到
加密的数据只有客户端和服务器端才能得到明文+
客户端到服务端的通信是安全的。

爬虫工作流程

网络爬虫是捜索引擎Baidu、Google、Yahoo抓取系统的重要组成部分。主要目的是将互联网上的网页下载到本地形成一个互联网内容的镜像备份。
网络爬虫的基本工作流程如下
1.首先选取一部分精心挑选的种子URL
2.将这些URL放入待抓取URL队列
3.从待抓取URL队列中取出待抓取在URL解析DNS并且得到主机的ip将URL对应的网页下载下来并存储到已下载网页库中。此外将这些URL放进已抓取URL队列。
4.分析已抓取URL队列中的URL分析其中的其他URL并且将URL放入待抓取URL队列从而进入下一个循环。

import requests                       #用来爬取网页
from bs4 import BeautifulSoup         #用来解析网页
#我们的种子
seds = ["http://www.lagou.com/"]
sum = 0
#我们设定终止条件为爬取到10000个页面时就不玩了
while sum < 10000 :
    if sum < len(seds):
         r = requests.get(seds[sum])
         sum = sum + 1
         #提取结构化数据做存储操作
         do_save_action(r)
         soup = BeautifulSoup(r.content)               
         urls = soup.find_all("href",.....)  //解析网页所有的链接
         for url in urls:
              seds.append(url)
    else:
         break
# -*- coding:utf-8 -*-
import os
import requests
from bs4 import BeautifulSoup

def spider():
    url = "http://bj.grfy.net/"
    proxies = {"http": "http://172.17.18.80:8080", "https": "https://172.17.18.80:8080"}
    r = requests.get(url, proxies=proxies)
    html = r.content
    soup = BeautifulSoup(html, "lxml")
    divs = soup.find_all("div", class_="content")
    print len(divs)
    print soup

if __name__ == "__main__":
    spider()
    os.system("pause")

HTTP代理神器Fidder

Fiddler不但能截获各种浏览器发出的HTTP请求, 也可以截获各种智能手机发出的HTTP/HTTPS请求。 Fiddler能捕获IOS设备发出的请求比如IPhone, IPad, MacBook. 等等苹果的设备。 同理也可以截获AndriodWindows Phone的等设备发出的HTTP/HTTPS。工作原理Fiddler 是以代理web服务器的形式工作的它使用代理地址:127.0.0.1端口:8888。

Fiddler抓取HTTPS设置启动Fiddler打开菜单栏中的 Tools > Fiddler Options打开“Fiddler Options”对话框。


对Fiddler进行设置打开工具栏->Tools->Fiddler Options->HTTPS


选中Capture HTTPS CONNECTs因为我们要用Fiddler获取手机客户端发出的HTTPS请求所以中间的下拉菜单中选中from remote clients only。选中下方Ignore server certificate errors.

配置Fiddler允许远程连接Fiddler 主菜单 Tools -> Fiddler Options…-> Connections页签选中Allow remote computers to connect。重启Fidler这一步很重要必须做。

Fiddler 如何捕获Chrome的会话switchyomega安装插件
工具http://www.is.org/    https://github.com/getlantern/lantern
打开网址https://chrome.google.com/webstore/search/switchyomega?hl=zh-CN


Fiddler 如何捕获Firefox的会话
能支持HTTP代理的任意程序的数据包都能被Fiddler嗅探到Fiddler的运行机制其实就是本机上监听8888端口的HTTP代理。 Fiddler2启动的时候默认IE的代理设为了127.0.0.1:8888而其他浏览器是需要手动设置的所以将Firefox的代理改为127.0.0.1:8888就可以监听数据了。 Firefox 上通过如下步骤设置代理 点击: Tools -> Options, 在Options 对话框上点击Advanced tab - > network tab -> setting.
Fiddler如何捕获HTTPS会话
默认下Fiddler不会捕获HTTPS会话需要你设置下 打开Fiddler Tool->Fiddler Options->HTTPS tab


选中checkbox 弹出如下的对话框点击"YES"



点击"Yes" 后就设置好了。

Fiddler的基本界面


特别注意 遇到这个Click请点击Click 


Fiddler强大的Script系统
Fiddler包含了一个强大的基于事件脚本的子系统并且能使用.net语言进行扩展。 
官方的帮助文档: http://www.fiddler2.com/Fiddler/dev/ScriptSamples.asp
首先先安装SyntaxView插件Inspectors tab->Get SyntaxView tab->Download and Install SyntaxView Now... 如下图


安装成功后Fiddler 就会多了一个Fiddler Script tab如下图


在里面我们就可以编写脚本了 看个实例让所有cnblogs的会话都显示红色。 把这段脚本放在OnBeforeRequest(oSession: Session) 方法下并且点击"Save script"

if (oSession.HostnameIs("www.cnblogs.com")) {
  oSession["ui-color"] = "red";
}
这样所有的cnblogs的会话都会显示红色。

HTTP协议介绍

设计HTTP(HyperText Transfer Protocol)是为了提供一种发布和接收HTML(HyperText Markup Language)页面的方法。
Http两部分组成请求、响应。


客户端请求消息客户端发送一个HTTP请求到服务器的请求消息包括以下格式请求行request line、请求头部header、空行和请求数据四个部分组成下图给出了请求报文的一般格式。


服务器响应消息HTTP响应也由四个部分组成分别是状态行、消息报头、空行和响应正文。


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

HTTP 请求


请求方法
根据HTTP标准HTTP请求可以使用多种请求方法。
HTTP1.0定义了三种请求方法 GET, POST 和 HEAD方法。
HTTP1.1新增了五种请求方法OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。

序号

方法

描述

1

GET

请求指定的页面信息并返回实体主体。

2

HEAD

类似于get请求只不过返回的响应中没有具体的内容用于获取报头

3

POST

向指定资源提交数据进行处理请求例如提交表单或者上传文件。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。

4

PUT

从客户端向服务器传送的数据取代指定的文档的内容。

5

DELETE

请求服务器删除指定的页面。

6

CONNECT

HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。

7

OPTIONS

允许客户端查看服务器的性能。

8

TRACE

回显服务器收到的请求主要用于测试或诊断。

GET和POST方法区别归纳如下几点
1. GET是从服务器上获取数据POST是向服务器传送数据。
2. GET请求参数显示都显示在浏览器网址上,POST请求参数在请求体当中,消息长度没有限制而且以隐式的方式进行发送
3. 尽量避免使用Get方式提交表单因为有可能会导致安全问题。 比如说在登陆表单中用Get方式用户输入的用户名和密码将在地址栏中暴露无遗。 但是在分页程序中用Get方式就比用Post好。
URL概述

统一资源定位符URL英语 Uniform / Universal Resource Locator的缩写是用于完整地描述Internet上网页和其他资源的地址的一种标识方法。
URL格式基本格式如下 schema://host[:port#]/path/…/[?query-string][#anchor]


1. schema 协议(例如http, https, ftp)
2. host 服务器的IP地址或者域名
3. port# 服务器的端口如果是走协议默认端口缺省端口80
4. path 访问资源的路径
5. query-string 参数发送给http服务器的数据
6. anchor- 锚跳转到网页的指定锚点位置
例子:
http://www.sina.com.cn/
http://192.168.0.116:8080/index.jsp
http://item.jd.com/11052214.html#product-detail
http://www.website.com/test/test.aspx?name=sv&x=true#stuff

一个URL的请求过程
当你在浏览器输入URL http://www.website.com 的时候浏览器发送一个Request去获取 http://www. website.com的html. 服务器把Response发送回给浏览器. 浏览器分析Response中的 HTML发现其中引用了很多其他文件比如图片CSS文件JS文件。 浏览器会自动再次发送Request去获取图片CSS文件或者JS文件。 当所有的文件都下载成功后 网页就被显示出来了。
常用的请求报头
HostHost初始URL中的主机和端口,用于指定被请求资源的Internet主机和端口号它通常从HTTP URL中提取出来的
Connection表示客户端与服务连接类型
1. client 发起一个包含Connection:keep-alive的请求
2. server收到请求后如果server支持keepalive回复一个包含Connection:keep-alive的响应不关闭连接否则回复一个包含Connection:close的响应关闭连接。
3. 如果client收到包含Connection:keep-alive的响应向同一个连接发送下一个请求直到一方主动关闭连接。 Keep-alive在很多情况下能够重用连接减少资源消耗缩短响应时间HTTP
Accept表示浏览器支持的 MIME 类型
MIME的英文全称是 Multipurpose Internet Mail Extensions多用途互联网邮件扩展
eg
Acceptimage/gif表明客户端希望接受GIF图象格式的资源
Accepttext/html表明客户端希望接受html文本。
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
意思浏览器支持的 MIME 类型分别是 text/html、application/xhtml+xml、application/xml 和 */*优先顺序是它们从左到右的排列顺序。
Text用于标准化地表示的文本信息文本消息可以是多种字符集和或者多种格式的
Application用于传输应用程序数据或者二进制数据设定某种扩展名的文件用一种应用程序来打开的方式类型当该扩展名文件被访问的时候浏览器会自动使用指定应用程序来打开

Mime类型

扩展名

text/html

.htm .html *.shtml

text/plain

text/html是以html的形式输出比如就会在页面上显示一个文本框而以plain形式就会在页面上原样显示这段代码

application/xhtml+xml

.xhtml .xml

text/css

*.css

application/msexcel

.xls .xla

application/msword

.doc .dot

application/octet-stream

*.exe

application/pdf

*.pdf

.....

.....

q是权重系数范围 0 =< q <= 1q 值越大请求越倾向于获得其“;”之前的类型表示的内容若没有指定 q 值越大请求越倾向于获得其“则默认为1若被赋值为0则用于提醒服务器哪些是浏览器不接受的内容类型。
Content-TypePOST 提交application/x-www-form-urlencoded 提交的数据按照 key1=val1&key2=val2 的方式进行编码key 和 val 都进行了 URL 转码。
User-Agent 浏览器类型
Referer 请求来自哪个页面用户是从该 Referer URL页面访问当前请求的页面。
Accept-Encoding浏览器支持的压缩编码类型比如gzip,支持gzip的浏览器返回经gzip编码的HTML页面。许多情形下这可以减少5到10倍的下载时间
eg
Accept-Encoding:gzip;q=1.0, identity; q=0.5, *;q=0 // 按顺序支持 gzip , identity
如果有多个Encoding同时匹配, 按照q值顺序排列
如果请求消息中没有设置这个域服务器假定客户端对各种内容编码都可以接受。
Accept-Language浏览器所希望的语言种类当服务器能够提供一种以上的语言版本时要用到。
eg
Accept-Language:zh-cn
如果请求消息中没有设置这个报头域服务器假定客户端对各种语言都可以接受。
Accept-Charset浏览器可接受的字符集,用于指定客户端接受的字符集
eg
Accept-Charset:iso-8859-1,gb2312
ISO8859-1通常叫做Latin-1。Latin-1包括了书写所有西方欧洲语言不可缺少的附加字符;
gb2312是标准中文字符集;
UTF-8 是 UNICODE 的一种变长字符编码可以解决多种语言文本显示问题从而实现应用国际化和本地化。
如果在请求消息中没有设置这个域缺省是任何字符集都可以接受。

HTTP 响应

掌握常用的响应状态码


服务器上每一个HTTP 应答对象response包含一个数字"状态码"。
有时状态码指出服务器无法完成请求。默认的处理器会为你处理一部分这种应答。
例如:假如response是一个"重定向"需要客户端从别的地址获取文档urllib2将为你处理。
其他不能处理的urlopen会产生一个HTTPError。
典型的错误包含"404"(页面无法找到)"403"(请求禁止)和"401"(带验证请求)。
HTTP状态码表示HTTP协议所返回的响应的状态。
比如客户端向服务器发送请求如果成功地获得请求的资源则返回的状态码为200表示响应成功。
如果请求的资源不存在 则通常返回404错误。 

HTTP响应状态码通常分为5种类型分别以15五个数字开头由3位整数组成第一个数字定义了响应的类别

分类

分类描述

1**

信息服务器收到请求需要请求者继续执行操作

2**

成功操作被成功接收并处理

3**

重定向需要进一步的操作以完成请求

4**

客户端错误请求包含语法错误或无法完成请求

5**

服务器错误服务器在处理请求的过程中发生了错误

最常用的响应状态码
200 (OK): 请求成功找到了该资源并且一切正常。处理方式获得响应的内容进行处理 
201 请求完成结果是创建了新资源。新创建资源的URI可在响应的实体中得到    处理方式爬虫中不会遇到 
202 请求被接受但处理尚未完成    处理方式阻塞等待 
204 服务器端已经实现了请求但是没有返回新的信 息。如果客户是用户代理则无须为此更新自身的文档视图。    处理方式丢弃
300 该状态码不被HTTP/1.0的应用程序直接使用 只是作为3XX类型回应的默认解释。存在多个可用的被请求资源。    处理方式若程序中能够处理则进行进一步处理如果程序中不能处理则丢弃
301 (Moved Permanently): 请求的文档在其他地方新的URL在Location头中给出浏览器应该自动地访问新的URL。处理方式重定向到分配的URL
302 (Found): 类似于301但新的URL应该被视为临时性的替代而不是永久性的。处理方式重定向到临时的URL 
304 (NOT MODIFIED): 该资源在上次请求之后没有任何修改。这通常用于浏览器的缓存机制。处理方式丢弃 
400 (Bad Request): 请求出现语法错误。非法请求     处理方式丢弃 
401 未授权     处理方式丢弃 
403 (FORBIDDEN): 客户端未能获得授权。这通常是在401之后输入了不正确的用户名或密码。禁止访问    处理方式丢弃 
404 (NOT FOUND): 在指定的位置不存在所申请的资源。没有找到 。    处理方式丢弃 
5XX 回应代码以“5”开头的状态码表示服务器端发现自己出现错误不能继续执行请求    处理方式丢弃
500 (Internal Server Error): 服务器遇到了意料不到的情况不能完成客户的请求
503 (Service Unavailable): 服务器由于维护或者负载过重未能应答。
例如Servlet可能在数据库连接池已满的情况下返回503。服务器返回503时可以提供一个Retry-After头

常用的响应报头(了解)
Location表示客户应当到哪里去提取文档,用于重定向接受者到一个新的位置
Server服务器名字包含了服务器用来处理请求的软件信息
eg: Server响应报头域的一个例子
ServerApache-Coyote/1.1
Set-Cookie设置和页面关联的Cookie。
例如前一个 cookie 被存入浏览器并且浏览器试图请求 http://www.ibm.com/foo/index.html 时
Set-Cookiecustomer=huangxp; path=/foo; domain=.ibm.com; 
expires= Wednesday, 19-OCT-05 23:12:40 GMT;
Set-Cookie的每个属性解释如下
Customer=huangxp 一个"名称值"对把名称customer设置为值"huangxp"这个属性在Cookie中必须有。
path=/foo 服务器路径。
domain=.ibm.com 指定cookie 的域名。
expires= Wednesday, 19-OCT-05 23:12:40 GMT 指定cookie 失效的时间

使用 Urllib2库

urllib2是python2.7自带的模块(不需要下载),它支持多种网络协议比如 FTP、HTTP、HTTPS等。 urllib2在python3.x中被改为urllib.request
利用urllib2提供了一个接口 urlopen函数
urllib2 官方文档 https://docs.python.org/2/library/urllib2.html
urlopen(url, data, timeout,....)
1第一个参数url即为URL第一个参数URL是必须要传送的
2第二个参数data是访问URL时要传送的数据data默认为空None
3第三个timeout是设置超时时间timeout默认为 60ssocket._GLOBAL_DEFAULT_TIMEOUT

GET请求方式
以传智播客官方网站 http://www.itcast.cn

import urllib2
response = urllib2.urlopen('http://www.itcast.cn/')
data = response.read()
print data
print response.code
保存成 demo.py进入该文件的目录执行如下命令查看运行结果 python demo.py


如果我想添加 Header信息怎么办 利用 urllib2.Request类

利用urllib2.Request方法,可以用来构造一个Http请求消息
help(urllib2.Request)


正则headers 转dict
^(.*):\s(.*)$
"\1":"\2",
# -*- coding: utf-8 -*-
import urllib2

get_headers={
      'Host': 'www.itcast.cn',
      'Connection': 'keep-alive',
      'Pragma': 'no-cache',
      'Cache-Control': 'no-cache',
      'Upgrade-Insecure-Requests': '1',
      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
      #此处是压缩算法不便于查看要做解压
      #'Accept-Encoding': 'gzip, deflate, sdch',
      'Accept-Language': 'zh-CN,zh;q=0.8',
      'Cookie': 'pgv_pvi=7044633600; tencentSig=6792114176; IESESSION=alive; pgv_si=s3489918976; CNZZDATA4617777=cnzz_eid%3D768417915-1468987955-%26ntime%3D1470191347; _qdda=3-1.1; _qddab=3-dyl6uh.ireawgo0; _qddamta_800068868=3-0'
 }
request = urllib2.Request("http://www.itcast.cn/",headers=get_headers)

#request.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36')

response = urllib2.urlopen(request)
print response.code
data = response.read()
print data
提问为什么这两种写法都对
一个headers没写另一个写了都好使 原因是web服务器能够理解请求数据并且没有做验证机制
POST请求方式
抓取拉钩招聘信息 http://www.lagou.com/jobs/list_?px=new&city=%E5%85%A8%E5%9B%BD#order
# -*- coding: utf-8 -*-
import urllib2
import urllib

proxy_handler = urllib2.ProxyHandler({"http" : 'http://192.168.17.1:8888'})
opener = urllib2.build_opener(proxy_handler)
urllib2.install_opener(opener)

Sum = 1
output = open('lagou.json', 'w')
for page in range(1,Sum+1): 

      formdata = 'first=false&pn='+str(page)+'&kd='
      print '运行到第 (%2d) 页面' %(page)

      send_headers={
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'Accept': 'application/json, text/javascript, */*; q=0.01',
           ' X-Requested-With': 'XMLHttpRequest'
      }
      request =urllib2.Request('http://www.lagou.com/jobs/positionAjax.json?px=new&needAddtionalResult=false',headers=send_headers)
      #request.add_header('X-Requested-With','XMLHttpRequest')
      #request.headers=send_headers
      request.add_data(formdata)

      print request.get_data()
      response = urllib2.urlopen(request)

      print response.code
      resHtml =response.read()
      #print resHtml
      output.write(resHtml+'\n')
output.close()
print '-'*4 + 'end'+'-'*4


提出一个问题如果要采集的是 拉钩招聘网站 北京>>朝阳区>>望京 以这个网站为例该如何理解这个url

http://www.lagou.com/jobs/list_?px=default&city=%E5%8C%97%E4%BA%AC&district=%E6%9C%9D%E9%98%B3%E5%8C%BA&bizArea=%E6%9C%9B%E4%BA%AC#filterBox
urlencode编码/解码在线工具

# -*- coding: utf-8 -*-
import urllib2
import urllib
query = {
      'city':'北京',
      'district':'朝阳区',
      'bizArea':'望京'
}
print urllib.urlencode(query)
page =3
values = {
      'first':'false',
      'pn':str(page),
      'kd':'后端开发',
}
formdata = urllib.urlencode(values)
print formdata
小结
Content-Length 是指报头Header以外的内容长度指 表单数据长度
X-Requested-With: XMLHttpRequest 表示Ajax异步请求
Content-Type: application/x-www-form-urlencoded 表示提交的表单数据 会按照name/value 值对 形式进行编码
例如name1=value1&name2=value2... 。name 和 value 都进行了 URL 编码utf-8、gb2312
在线测试字符串长度

作业

  • 常用的请求报头Header、Content-Type、响应报头
  • 常用请求方式GET、POST
  • gzip、deflate压缩
  • 网页编码格式和自动识别
  • fidder工具使用
  • urllib2、urllib
  • HTTP代理
  • requests模块
  • Beautiful Soup
URL和URI的区别
URL:统一资源定位符uniform resource location;平时上网时在 IE 浏览器中输入的那个地址就是 URL。比如网易 http://www.163.com 就是一个URL 。
URI:统一资源标识符uniform resource identifier。Web 上可用的每种资源 - HTML 文档、图像、视频片段、程序, 由一个通过通用资源标志符 (Universal Resource Identifier, 简称 "URI") 进行定位。
URL是Internet上用来描述信息资源的字符串主要用在各种WWW客户程序和服务器程序上。采用URL可以用一种统一的格式来描述各种信息资源包括文件、服务器的地址和目录等。
URI 是个纯粹的语法结构用于指定标识web资源的字符串的各个不同部分URL 是URI的一个特例它包含定位web资源的足够信息。
URL 是 URI 的一个子集

常见的加密、解密
1) MD5/SHA (单向密散列算法(Hash函数)) MessageDigest是一个数据的数字指纹.即对一个任意长度的数据进行计算,产生一个唯一指纹号. MessageDigest的特性: A) 两个不同的数据,难以生成相同的指纹号 B) 对于指定的指纹号,难以逆向计算出原始数据 代表:MD5/SHA
2) DES、AES、TEA(对称加密算法) 单密钥算法,是信息的发送方采用密钥A进行数据加密,信息的接收方采用同一个密钥A进行数据解密. 单密钥算法是一个对称算法. 缺点:由于采用同一个密钥进行加密解密,在多用户的情况下,密钥保管的安全性是一个问题. 代表:DES
3) RSA(非对称算法) RSA 是一种非对称加解密算法。 RSA is named from the initials of the authors, Ron Rivest, Adi Shamir, and Leonard Adleman,who first published the algorithm.
RSA 与 DSA 都是非对称加密算法。其中RSA的安全性是基于极其困难的大整数的分解两个素数的乘积DSA 的安全性 是基于整数有限域离散对数难题。基本上可以认为相同密钥长度的 RSA 算法与 DSA 算法安全性相当。 公钥用于加密它是向所有人公开的私钥用于解密只有密文的接收者持有。 适用OPENSSL 适用RSA 的命令如下 生成一个密钥(私钥) [root@hunterfu ~]# openssl genrsa -out private.key 1024 注意: 需要注意的是这个文件包含了公钥和密钥两部分也就是说这个文件即可用来加密也可以用来解密,后面的1024是生成 密钥的长度. 通过密钥文件private.key 提取公钥
[root@hunterfu ~]# openssl rsa -in private.key -pubout -out pub.key
使用公钥加密信息
[root@hunterfu ~]# echo -n "123456" | openssl rsautl -encrypt -inkey pub.key -pubin >encode.result
使用私钥解密信息
[root@hunterfu ~]#cat encode.result | openssl rsautl -decrypt -inkey private.key
123456
4) DSA (Digital Signature Algorithm)(数字签名算法)(非对称加密) DSA 一般用于数字签名和认证。 DSA是Schnorr和ElGamal签名算法的变种被美国NIST作为DSS(DigitalSignature Standard)。 DSA是基于整数有限域离散对数难题的其安全性与RSA相比差不多。 在DSA数字签名和认证中发送者使用自己的私钥对文件或消息进行签名接受者收到消息后使用发送者的公钥 来验证签名的真实性。DSA只是一种算法和RSA不同之处在于它不能用作加密和解密也不能进行密钥交换 只用于签名,它比RSA要快很多. 生成一个密钥(私钥)
[root@hunterfu ~]# openssl dsaparam -out dsaparam.pem 1024
[root@hunterfu ~]# openssl gendsa -out privkey.pem dsaparam.pem
生成公钥
[root@hunterfu ~]# openssl dsa -in privkey.pem -out pubkey.pem -pubout 
[root@hunterfu ~]# rm -fr dsaparam.pem
# rm -fr == rm -rf == rm -r -f没区别。
使用私钥签名
[root@hunterfu ~]# echo -n "123456" | openssl dgst -dss1 -sign privkey.pem > sign.result
使用公钥验证
[root@hunterfu ~]# echo -n "123456" | openssl dgst -dss1 -verify pubkey.pem -signature sign.result

爬虫入门基础篇

数据格式

描述

设计目标

XML

Extensible Markup Language 可扩展标记语言

被设计为传输和存储数据其焦点是数据的内容

HTML

HyperText Markup Language超文本标记语言

显示数据以及如何更好显示数据

HTML DOM

HTML Document Object Model(文档对象模型)

通过 JavaScript您可以重构整个HTML文档。您可以添加、移除、改变或重排页面上的项目。要改变页面的某个东西JavaScript就需要对HTML文档中所有元素进行访问的入口。

XML 示例


 
   
    Everyday Italian  
    Giada De Laurentiis  
    2005  
    30.00 
    
   
    Harry Potter  
    J K. Rowling  
    2005  
    29.99 
    
   
    XQuery Kick Start  
    James McGovern  
    Per Bothner  
    Kurt Cagle  
    James Linn  
    Vaidyanathan Nagarajan  
    2003  
    49.99 
    
   
    Learning XML  
    Erik T. Ray  
    2003  
    39.95 
   
XML DOM 定义访问和操作XML文档的标准方法。 DOM 将 XML 文档作为一个树形结构而树叶被定义为节点。


HTML DOM 示例

HTML DOM 定义了访问和操作 HTML 文档的标准方法。 DOM 以树结构表达 HTML 文档。

页面解析之数据提取

一般来讲对我们而言需要抓取的是某个网站或者某个应用的内容提取有用的价值内容一般分为两部分非结构化的文本或结构化的文本。
结构化的数据JSON、XML
非结构化的数据HTML文本包含JavaScript代码

HTML文本包含JavaScript代码是最常见的数据格式理应属于结构化的文本组织但因为一般我们需要的关键信息并非直接可以得到需要进行对HTML的解析查找甚至一些字符串操作才能得到所以还是归类于非结构化的数据处理中。
把网页比作一个人那么HTML便是他的骨架JS便是他的肌肉CSS便是它的衣服。

常见解析方式如下XPath、CSS选择器、正则表达式。
一段文本
例如一篇文章或者一句话我们的初衷是提取有效信息所以如果是滞后处理可以直接存储如果是需要实时提取有用信息常见的处理方式如下
分词 根据抓取的网站类型使用不同词库进行基本的分词然后变成词频统计类似于向量的表示词为方向词频为长度。
NLP 自然语言处理进行语义分析用结果表示例如正负面等。

非结构化数据之XPath

XPath 语言XPathXML Path Language是XML路径语言,它是一种用来定位XML文档中某部分位置的语言。
将HTML转换成XML文档之后用XPath查找HTML节点或元素
比如用“/”来作为上下层级间的分隔第一个“/”表示文档的根节点注意不是指文档最外层的tag节点而是指文档本身。
比如对于一个HTML文件来说最外层的节点应该是"/html"。
XPath开发工具开源的XPath表达式编辑工具:XMLQuire(XML格式文件可用)、chrome插件 XPath Helper


firefox插件 XPath Checker


XPath语法参考文档http://www.w3school.com.cn/xpath/index.asp

XPath语法
XPath 是一门在 XML 文档中查找信息的语言。
XPath 可用来在 XML 文档中对元素和属性进行遍历。




Harry Potter
29.99


Learning XML
39.95

选取节点 XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
下面列出了最有用的路径表达式

表达式

描述

/

从根节点选取。

nodename

选取此节点的所有子节点。

//

从当前节点 选择 所有匹配文档中的节点

.

选取当前节点。

..

选取当前节点的父节点。

@

选取属性。

在下面的表格中我们已列出了一些路径表达式以及表达式的结果

路径表达式

结果

/bookstore

选取根元素 bookstore。注释假如路径起始于正斜杠( / )则此路径始终代表到某元素的绝对路径

bookstore

选取 bookstore 元素的所有子节点。默认从根节点选取

bookstore/book

选取属于 bookstore 的子元素的所有 book 元素。

//book

选取所有 book 子元素而不管它们在文档中的位置。

//book/./title

选取所有 book 子元素从当前节点查找title节点

//price/..

选取所有 book 子元素从当前节点查找父节点

bookstore//book

选择属于 bookstore 元素的后代的所有 book 元素而不管它们位于 bookstore 之下的什么位置。

//@lang

选取名为 lang 的所有属性。

谓语条件Predicates
谓语用来查找某个特定的信息或者包含某个指定的值的节点。
所谓"谓语条件"就是对路径表达式的附加条件
谓语是被嵌在方括号中都写在方括号"[]"中表示对节点进行进一步的筛选。
在下面的表格中我们列出了带有谓语的一些路径表达式以及表达式的结果

路径表达式

结果

/bookstore/book[1]

选取属于 bookstore 子元素的第一个 book 元素。

/bookstore/book[last()]

选取属于 bookstore 子元素的最后一个 book 元素。

/bookstore/book[last()-1]

选取属于 bookstore 子元素的倒数第二个 book 元素。

/bookstore/book[position()<3]

选取最前面的两个属于 bookstore 元素的子元素的 book 元素。

//title[@lang]

选取所有拥有名为 lang 的属性的 title 元素。

//title[@lang=’eng’]

选取所有 title 元素且这些元素拥有值为 eng 的 lang 属性。

//book[price]

选取所有 book 元素且被选中的book元素必须带有price子元素

/bookstore/book[price>35.00]

选取 bookstore 元素的所有 book 元素且其中的 price 元素的值须大于 35.00。

/bookstore/book[price>35.00]/title

选取 bookstore 元素中的 book 元素的所有 title 元素且其中的 price 元素的值须大于 35.00

选取未知节点XPath 通配符可用来选取未知的 XML 元素。

通配符

描述

*

匹配任何元素节点。

@*

匹配任何属性节点。

在下面的表格中我们列出了一些路径表达式以及这些表达式的结果

路径表达式

结果

/bookstore/*

选取 bookstore 元素的所有子元素。

//*

选取文档中的所有元素。

//title[@*]

选取所有带有属性的 title 元素。

选取若干路径 通过在路径表达式中使用“|”运算符您可以选取若干个路径。
在下面的表格中我们列出了一些路径表达式以及这些表达式的结果

路径表达式

结果

//book/title | //book/price

选取 book 元素的所有 title 和 price 元素。

//title | //price

选取文档中的所有 title 和 price 元素。

/bookstore/book/title | //price

选取属于 bookstore 元素的 book 元素的所有 title 元素以及文档中所有的 price 元素。

XPath 高级用法

模糊查询 contains
目前许多web框架都是动态生成界面的元素id因此在每次操作相同界面时ID都是变化的这样为自动化测试造成了一定的影响。

解决方法 使用xpath的匹配功能//input[contains(@id,'nt')]

测试使用的XML


张城斌
 [email protected] 
http://cbcye.cnblogs.com


Gary Zhang
 [email protected]
http://www.quicklearn.cn

1.查询所有Blog节点值中带有 cn 字符串的Person节点。Xpath表达式/Root//Person[contains(Blog,'cn')]
2.查询所有Blog节点值中带有 cn 字符串并且属性ID值中有01的Person节点。Xpath表达式/Root//Person[contains(Blog,'cn') and contains(@ID,'01')]

学习笔记

1.依靠自己的属性文本定位
   //td[text()='Data Import']
   //div[contains(@class,'cux-rightArrowIcon-on')]
   //a[text()='马上注册']
   //input[@type='radio' and @value='1']     多条件
   //span[@name='bruce'][text()='bruce1'][1]   多条件
   //span[@id='bruce1' or text()='bruce2']  找出多个
   //span[text()='bruce1' and text()='bruce2']  找出多个
2.依靠父节点定位
  //div[@class='x-grid-col-name x-grid-cell-inner']/div
  //div[@id='dynamicGridTestInstanceformclearuxformdiv']/div
  //div[@id='test']/input
3.依靠子节点定位
  //div[div[@id='navigation']]
  //div[div[@name='listType']]
  //div[p[@name='testname']]
4.混合型
  //div[div[@name='listType']]//img
  //td[a//font[contains(text(),'seleleium2从零开始 视屏')]]//input[@type='checkbox']
5.进阶部分
   //input[@id='123']/following-sibling::input   找下一个兄弟节点
   //input[@id='123']/preceding-sibling::span    上一个兄弟节点
   //input[starts-with(@id,'123')]               以什么开头
   //span[not(contains(text(),'xpath')]        不包含xpath字段的span
6.索引
  //div/input[2]
  //div[@id='position']/span[3]
  //div[@id='position']/span[position()=3]
  //div[@id='position']/span[position()>3]
  //div[@id='position']/span[position()<3]
  //div[@id='position']/span[last()]
  //div[@id='position']/span[last()-1]
7.substring 截取判断
  
//*[substring(@id,4,5)='Every']/@id 截取该属性 定位3,取长度5的字符 //*[substring(@id,4)='EveryCookieWrap'] 截取该属性从定位3 到最后的字符 //*[substring-before(@id,'C')='swfEvery']/@id 属性 'C'之前的字符匹配 //*[substring-after(@id,'C')='ookieWrap']/@id 属性'C之后的字符匹配 8.通配符* //span[@*='bruce'] //*[@name='bruce'] 9.轴 //div[span[text()='+++current node']]/parent::div 找父节点 //div[span[text()='+++current node']]/ancestor::div 找祖先节点 10.孙子节点 //div[span[text()='current note']]/descendant::div/span[text()='123'] //div[span[text()='current note']]//div/span[text()='123'] 两个表达的意思一样 11.following pre https://www.baidu.com/s?wd=xpath&pn=10&oq=xpath&ie=utf-8&rsv_idx=1&rsv_pq=df0399f30003691c&rsv_t=7dccXo734hMJVeb6AVGfA3434tA9U%2FXQST0DrOW%2BM8GijQ8m5rVN2R4J3gU //span[@class="fk fk_cur"]/../following::a 往下的所有a //span[@class="fk fk_cur"]/../preceding::a[1] 往上的所有a xpath提取多个标签下的text 在写爬虫的时候经常会使用xpath进行数据的提取对于如下的代码
大家好
使用xpath提取是非常方便的。假设网页的源代码在selector中 data = selector.xpath('//div[@id="test1"]/text()').extract()[0] 就可以把“大家好”提取到data变量中去。 然而如果遇到下面这段代码呢
美女你的微信是多少
如果使用 data = selector.xpath('//div[@id="test2"]/text()').extract()[0] 只能提取到“美女” 如果使用 data = selector.xpath('//div[@id="test2"]/font/text()').extract()[0] 又只能提取到“你的微信是多少” 可是我本意是想把“美女你的微信是多少”这一整个句子提取出来。
我左青龙右白虎
    上朱雀
  • 下玄武。
老牛在当中
龙头在胸口。
而且内部的标签还不固定如果我有一百段这样类似的html代码又如何使用xpath表达式以最快最方便的方式提取出来 使用xpath的string(.) 以第三段代码为例 data = selector.xpath('//div[@id="test3"]') info = data.xpath('string(.)').extract()[0] 这样就可以把“我左青龙右白虎上朱雀下玄武。老牛在当中龙头在胸口”整个句子提取出来赋值给info变量。

非结构化数据之lxml库

lxml 是一种使用 Python 编写的库,可以迅速、灵活地处理 XML 支持 XPath (XML Path Language)
lxml python 官方文档 http://lxml.de/index.html
学习目的利用上节课学习的XPath语法来快速的定位 特定元素以及节点信息目的是 提取出 HTML、XML 目标数据
如何安装
Ubuntu :
sudo apt-get install libxml2-dev libxslt1-dev python-dev
sudo apt-get install zlib1g-dev
sudo apt-get install libevent-dev
sudo pip install lxml //利用 pip 安装即可
Windows:
http://blog.csdn.net/g1apassz/article/details/46574963
http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml
初步使用
首先我们利用lxml来解析 HTML 代码先来一个小例子来感受一下它的基本用法。
使用 lxml 的 etree 库然后利用 etree.HTML 初始化然后我们将其打印出来。

from lxml import etree
text = '''

'''
#Parses an HTML document from a string
html = etree.HTML(text)   
#Serialize an element to an encoded string representation of its XML tree
result = etree.tostring(html)
print result
所以输出结果是这样的

不仅补全了 li 标签还添加了 bodyhtml 标签。

XPath实例测试

1获取所有的

  • 标签
    print type(html)
    result = html.xpath('//li')
    print result
    print len(result)
    print type(result)
    print type(result[0])

    运行结果

    [, , , , ]
    5


    可见每个元素都是 Element 类型;是一个个的标签元素类似现在的实例
    Element类型代表的就是
  • first item

  • 注意
    Element类型是一种灵活的容器对象用于在内存中存储结构化数据。
    每个element对象都具有以下属性
      1. tagstring对象标签用于标识该元素表示哪种数据即元素类型。
      2. attribdictionary对象表示附有的属性。
      3. textstring对象表示element的内容。
      4. tailstring对象表示element闭合之后的尾迹。
    实例
    texttail
    1       2               3                4
    result[0].tag
    result[0].text
    result[0].tail
    result[0].attrib
    2获取
  • 标签的所有 class
    html.xpath('//li/@class')
    运行结果:['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']
    3获取
  • 标签下属性 href 为 link1.html 的 标签
    html.xpath('//li/a[@href="link1.html"]')
    运行结果:[]
    4获取
  • 标签下的所有 标签
    注意这么写是不对的
    html.xpath('//li/span')
    因为 / 是用来获取子元素的而 并不是
  • 的子元素所以要用双斜杠
    html.xpath('//li//span')
    运行结果:[]
    5获取
  • 标签下的所有 class不包括

  • html.xpath('//li/a//@class')
    运行结果:['blod']
    6获取最后一个
  • 的 href
    html.xpath('//li[last()]/a/@href')
    运行结果:['link5.html']
    7获取 class 为 bold 的标签名
    result = html.xpath('//*[@class="bold"]')
    print result[0].tag
    运行结果:span

    实战项目

    以腾讯招聘网站为例http://hr.tencent.com/position.php?&start=10

    from lxml import etree
    import urllib2
    import urllib
    import json
    
    request = urllib2.Request('http://hr.tencent.com/position.php?&start=10#a')
    response =urllib2.urlopen(request)
    resHtml = response.read()
    output =open('tencent.json','w')
    
    html = etree.HTML(resHtml)
    result = html.xpath('//tr[@class="odd"] | //tr[@class="even"]')
    
    for site in result:
        item={}
        name = site.xpath('./td[1]/a')[0].text
        detailLink = site.xpath('./td[1]/a')[0].attrib['href']
        catalog = site.xpath('./td[2]')[0].text
        recruitNumber = site.xpath('./td[3]')[0].text
        workLocation = site.xpath('./td[4]')[0].text
        publishTime = site.xpath('./td[5]')[0].text
    
        print type(name)
        print name,detailLink,catalog,recruitNumber,workLocation,publishTime
        item['name']=name
        item['detailLink']=detailLink
        item['catalog']=catalog
        item['recruitNumber']=recruitNumber
        item['publishTime']=publishTime
    
        line = json.dumps(item,ensure_ascii=False) + '\n'
        print line
        output.write(line.encode('utf-8'))
    output.close()
    1练习一下lxml、etree、xpath的整个的操作
    2试试上节课XPath的语法以及Html自己动手实践

    非结构化数据之CSS SelectorCSS 选择器

    CSS(即层叠样式表Cascading Stylesheet) Selector来定位locate页面上的元素Elements。Selenium官网的Document里极力推荐使用CSS locator而不是XPath来定位元素原因是CSS locator比XPath locator速度快.

    Beautiful Soup

    支持从HTML或XML文件中提取数据的Python库。支持Python标准库中的HTML解析器。还支持一些第三方的解析器lxml, 使用的是 Xpath 语法推荐安装。

    Beautiful Soup自动将输入文档转换为Unicode编码输出文档转换为utf-8编码。你不需要考虑编码方式除非文档没有指定一个编码方式这时Beautiful Soup就不能自动识别编码方式了。然后你仅仅需要说明一下原始编码方式就可以了

    Beautiful Soup4 安装
    官方文档链接: https://www.crummy.com/software/BeautifulSoup/bs4/doc/
    可以利用 pip来安装pip install beautifulsoup4
    安装解析器(上节课已经安装过)
    Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml .根据操作系统不同,可以选择下列方法来安装lxml:
    另一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同,可以选择下列方法来安装html5lib: pip install html5lib

    下表列出了主要的解析器

    解析器

    使用方法

    优势

    劣势

    Python标准库

    BeautifulSoup(markup, "html.parser")

    Python的内置标准库;执行速度适中;文档容错能力强

    Python 2.7.3 or 3.2.2前 的版本中文档容错能力差

    lxml HTML 解析器

    BeautifulSoup(markup, "lxml")

    速度快;文档容错能力强 ;

    需要安装C语言库

    lxml XML 解析器

    BeautifulSoup(markup, ["lxml-xml"]) BeautifulSoup(markup, "xml")

    速度快;唯一支持XML的解析器

    需要安装C语言库

    html5lib

    BeautifulSoup(markup, "html5lib")

    最好的容错性;以浏览器的方式解析文档;生成HTML5格式的文档

    速度慢;不依赖外部扩展

    推荐使用lxml作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml或html5lib, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定.

    html_doc = """
    The Dormouse's story
    
    

    The Dormouse's story

    Once upon a time there were three little sisters; and their names were Elsie, Lacie and Tillie; and they lived at the bottom of a well.

    ...

    """
    使用BeautifulSoup解析这段代码,能够得到一个 BeautifulSoup 的对象,并能按照标准的缩进格式的结构输出:
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html_doc,'lxml')
    下面我们来打印一下 soup 对象的内容
    print soup

    格式化输出soup 对象

    print(soup.prettify())

    CSS选择器

    在写 CSS 时标签名不加任何修饰。类名前加点。id名前加“#”。 

    利用类似的方法来筛选元素用到的方法是 soup.select()返回类型是 list

    通过标签名查找

    print soup.select('title') 
    #[The Dormouse's story]
    
    print soup.select('a')
    #[, Lacie, Tillie]
    
    print soup.select('b')
    #[The Dormouse's story]
    通过类名查找
    print soup.select('.sister')
    #[, Lacie, Tillie]
    通过 id 名查找
    print soup.select('#link1')
    #[]
    直接子标签查找
    print soup.select("head > title")
    #[The Dormouse's story]
    组合查找
    组合查找即标签名与类名、id名进行的组合原理是一样的例如查找 p 标签中id 等于 link1的内容

    属性 和 标签 不属于 同一节点  二者需要用  空格分开

    print soup.select('p #link1')
    #[]

    属性查找

    查找时还可以加入属性元素属性需要用中括号括起来

    注意属性和标签属于同一节点所以中间不能加空格否则会无法匹配到

    print soup.select('a[class="sister"]')
    #[, Lacie, Tillie]
    
    print soup.select('a[href="http://example.com/elsie"]')
    #[]
    同样属性仍然可以与上述查找方式组合。不在同一节点的使用空格隔开在同一节点的则不用加空格

    print soup.select('p a[href="http://example.com/elsie"]')
    #[]
    以上的 select 方法返回的结果都是列表形式可以遍历形式输出

    用 get_text() 方法来获取它的内容。

    print soup.select('title')[0].get_text()
    for title in soup.select('title'):
        print title.get_text()
    Tag
    Tag 是什么通俗点讲就是 HTML 中的一个个标签例如
    Elsie
    print type(soup.select('a')[0])
    输出
    bs4.element.Tag
    对于 Tag它有两个重要的属性是 name 和 attrs下面我们分别来感受一下

    1. name

    print soup.name
    print soup.select('a')[0].name
    输出
    [document]
    'a'
    
    soup 对象本身比较特殊它的 name 即为 [document]对于其他内部标签输出的值便为标签本身的名称。

    2. attrs

    print soup.select('a')[0].attrs
    输出
    {'href': 'http://example.com/elsie', 'class': ['sister'], 'id': 'link1'}
    在这里我们把 soup.select('a')[0] 标签的所有属性打印输出了出来得到的类型是一个字典。
    如果我们想要单独获取某个属性可以这样例如我们获取它的 class 叫什么
    print soup.select('a')[0].attrs['class']
    输出
    ['sister']

    实战案例

    我们还是以 腾讯招聘网站http://hr.tencent.com/position.php?&start=10#a

    from bs4 import BeautifulSoup
    import urllib2
    import urllib
    import json
    
    request = urllib2.Request('http://hr.tencent.com/position.php?&start=10#a')
    response =urllib2.urlopen(request)
    resHtml = response.read()
    output =open('tencent.json','w')
    
    html = BeautifulSoup(resHtml,'lxml')
    result = html.select('tr[class="even"]')
    result2 = html.select('tr[class="odd"]')
    result+=result2
    print len(result)
    
    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']=detailLink
        item['catalog']=catalog
        item['recruitNumber']=recruitNumber
        item['publishTime']=publishTime
    
        line = json.dumps(item,ensure_ascii=False)
        print line
    
        output.write(line.encode('utf-8'))
    
    output.close()

    正则表达式

    掌握了XPath、CSS选择器为什么还要学习正则
    正则表达式用标准正则解析一般会把HTML当做普通文本用指定格式匹配当相关文本适合小片段文本或者某一串字符(比如电话号码、邮箱账户)或者 HTML包含javascript的代码无法用CSS选择器或者XPath

    在线正则表达式测试网站    官方文档

    正则表达式常见概念

    边界匹配
    ^ -- 与字符串开始的地方匹配不匹配任何字符
    $ -- 与字符串结束的地方匹配不匹配任何字符

      str = "cat abdcatdetf ios"
      ^cat : 验证该行以c开头紧接着是a然后是t
      ios$ : 验证该行以t结尾倒数第二个字符为a倒数第三个字符为c
      ^cat$: 以c开头接着是a->t然后是行结束只有cat三个字母的数据行
      ^$   : 开头之后马上结束空白行不包括任何字符
      ^    : 行的开头可以匹配任何行因为每个行都有行开头
    \b -- 匹配一个单词边界也就是单词和空格之间的位置不匹配任何字符
     "er\b"可以匹配"never"中的"er"但不能匹配"verb"中的"er"。
    \B -- \b取非即匹配一个非单词边界
    "er\B"能匹配"verb"中的"er"但不能匹配"never"中的"er"。
    数量词的贪婪模式与非贪婪模式

    正则表达式通常用于在文本中查找匹配的字符串。Python里数量词默认是贪婪的在少数语言里也可能是默认非贪婪总是尝试匹配尽可能多的字符非贪婪的则相反总是尝试匹配尽可能少的字符。例如

     正则表达式"ab*"如果用于查找"abbbc"将找到"abbb"。而如果使用非贪婪的数量词"ab*?"将找到"a"。

    反斜杠问题

    与大多数编程语言相同正则表达式里使用"\"作为转义字符这就可能造成反斜杠困扰。
    假如你需要匹配文本中的字符"\"那么使用编程语言表示的正则表达式里将需要4个反斜杠"\\\\"前两个和后两个分别用于在编程语言里转义成反斜杠转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。
    Python里的原生字符串很好地解决了这个问题这个例子中的正则表达式可以使用r"\\"表示。
    同样匹配一个数字的"\\d"可以写成r"\d"。有了原生字符串你再也不用担心是不是漏写了反斜杠写出来的表达式也更直观。

    import re
    a=re.search(r"\\","ab123bb\c")
    print a.group()
    \
    a=re.search(r"\d","ab123bb\c")
    print a.group()
    1
    Python Re模块
    Python 自带了re模块它提供了对正则表达式的支持。

    match函数re.match 尝试从字符串的起始位置匹配一个模式如果不是起始位置匹配成功的话match()就返回none。
    下面是此函数的语法re.match(pattern, string, flags=0)

    参数

    描述

    pattern

    这是正则表达式来进行匹配。

    string

    这是字符串这将被搜索匹配的模式在字符串的开头。

    flags

    标志位用于控制正则表达式的匹配方式如是否区分大小写多行匹配等等。

    匹配成功re.match方法返回一个匹配的对象否则返回None。 我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。

    匹配对象的方法

    描述

    group(num=0)

    此方法返回整个匹配或指定分组num

    groups()

    此方法返回所有元组匹配的子组空如果没有

    #!/usr/bin/python
    import re
    
    line = "Cats are smarter than dogs"
    
    matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I)
    
    if matchObj:
       print "matchObj.group() : ", matchObj.group()
       print "matchObj.group(1) : ", matchObj.group(1)
       print "matchObj.group(2) : ", matchObj.group(2)
    else:
       print "No match!!"
    当执行上面的代码它产生以下结果
    matchObj.group() :  Cats are smarter than dogs
    matchObj.group(1) :  Cats
    matchObj.group(2) :  smarter
    正则表达式修饰符 - 选项标志

    正则表达式字面可以包含一个可选的修饰符来控制匹配的各个方面。修饰符被指定为一个可选的标志。可以使用异或提供多个修饰符|如先前所示并且可以由这些中的一个来表示

    修饰符

    描述

    re.I(re.IGNORECASE)

    使匹配对大小写不敏感

    re.M(MULTILINE)

    多行匹配影响 ^ 和 $

    re.S(DOTALL)

    使 . 匹配包括换行在内的所有字符

    re.X(VERBOSE)

    正则表达式可以是多行忽略空白字符并可以加入注释

    findall()函数
    re.findall(pattern, string, flags=0)

    返回字符串中所有模式的非重叠的匹配作为字符串列表。该字符串扫描左到右并匹配返回的顺序发现

    默认
            pattren = "\w+"
            target = "hello world\nWORLD HELLO"
            re.findall(pattren,target)
            ['hello', 'world', 'WORLD', 'HELLO']
    
    re.I:   
            re.findall("world", target,re.I)
            ['world', 'WORLD']
    
    re.S:   
            re.findall("world.WORLD", target,re.S)
            ["world\nworld"]
            re.findall("hello.*WORLD", target,re.S)
            ['hello world\nWORLD']
    
    re.M:
            re.findall("^WORLD",target,re.M)
            ["WORLD"]
    
    re.X:
            reStr = '''\d{3}  #区号
                    -\d{8}''' #号码
            re.findall(reStr,"010-12345678",re.X) 
            ["010-12345678"]
    search函数
    re.search 扫描整个字符串并返回第一个成功的匹配。
    下面是此函数语法re.search(pattern, string, flags=0)

    参数

    描述

    pattern

    这是正则表达式来进行匹配。

    string

    这是字符串这将被搜索到的字符串中的任何位置匹配的模式。

    flags

    标志位用于控制正则表达式的匹配方式如是否区分大小写多行匹配等等。

    匹配成功re.search方法返回一个匹配的对象否则返回None。我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。

    #!/usr/bin/python
    import re
    
    line = "Cats are smarter than dogs";
    
    searchObj = re.search( r'(.*) are (.*?) .*', line, re.M|re.I)
    
    if searchObj:
       print "searchObj.group() : ", searchObj.group()
       print "searchObj.group(1) : ", searchObj.group(1)
       print "searchObj.group(2) : ", searchObj.group(2)
    else:
       print "Nothing found!!"
    当执行上面的代码它产生以下结果
    matchObj.group() :  Cats are smarter than dogs
    matchObj.group(1) :  Cats
    matchObj.group(2) :  smarter

    re.match与re.search的区别
    re.match只匹配字符串的开始如果字符串开始不符合正则表达式则匹配失败函数返回None而re.search匹配整个字符串直到找到一个匹配。

    #!/usr/bin/python
    import re
    
    line = "Cats are smarter than dogs";
    
    matchObj = re.match( r'dogs', line, re.M|re.I)
    if matchObj:
       print "match --> matchObj.group() : ", matchObj.group()
    else:
       print "No match!!"
    
    searchObj = re.search( r'dogs', line, re.M|re.I)
    if searchObj:
       print "search --> searchObj.group() : ", searchObj.group()
    else:
       print "Nothing found!!"
    当执行上面的代码产生以下结果
    No match!!
    search --> matchObj.group() :  dogs
    搜索和替换
    Python 的re模块提供了re.sub用于替换字符串中的匹配项。
    语法re.sub(pattern, repl, string, max=0)
    返回的字符串是在字符串中用 RE 最左边不重复的匹配来替换。如果模式没有发现字符将被没有改变地返回。 可选参数 count 是模式匹配后替换的最大次数count 必须是非负整数。缺省值是 0 表示替换所有的匹配。 实例

    #!/usr/bin/python
    import re
    
    url = "http://hr.tencent.com/position.php?&start=10"
    page = re.search('start=(\d+)',url).group(1)
    
    nexturl = re.sub(r'start=(\d+)', 'start='+str(int(page)+10), url)
    print "Next Url : ", nexturl
    当执行上面的代码产生以下结果
    Next Url :  http://hr.tencent.com/position.php?&start=20

    正则表达式语法


    页面解析之结构化数据

    结构化的数据是最好处理一般都是类似JSON格式的字符串直接解析JSON数据提取JSON的关键字段即可。

    JSON
    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式适用于进行数据交互的场景比如网站前台与后台之间的数据交互。
    Python 2.7中自带了JSON模块直接import json就可以使用了。
    Json模块提供了四个功能dumps、dump、loads、load,用于字符串 和 python数据类型间进行转换

    Python操作json的标准api库参考      在线JSON格式化代码

    1. json.loads()
    实现json字符串 转化 python的类型返回一个python的类型
    从json到python的类型转化对照如下


    import json
    
    a="[1,2,3,4]"
    b='{"k1":1,"k2":2}'#当字符串为字典时{}外面必须是''单引号{}里面必须是""双引号
    print json.loads(a) 
    [1, 2, 3, 4]
    
    print json.loads(b) 
    {'k2': 2, 'k1': 1}
    import urllib2
    import json
    
    response = urllib2.urlopen(r'http://api.douban.com/v2/book/isbn/9787218087351')
    
    hjson = json.loads(response.read())
    
    print hjson.keys()
    print hjson['rating']
    print hjson['images']['large']
    print hjson['summary']

    2. json.dumps()

    实现python类型转化为json字符串返回一个str对象
    从python原始类型向json类型的转化对照如下


    import json
    a = [1,2,3,4]
    b ={"k1":1,"k2":2}
    c = (1,2,3,4)
    
    json.dumps(a)
    '[1, 2, 3, 4]'
    
    json.dumps(b)
    '{"k2": 2, "k1": 1}'
    
    json.dumps(c)
    '[1, 2, 3, 4]'

    json.dumps 中的ensure_ascii 参数引起的中文编码问题

    如果Python Dict字典含有中文json.dumps 序列化时对中文默认使用的ascii编码

    import chardet
    import json
    
    b = {"name":"中国"}
    
    json.dumps(b)
    '{"name": "\\u4e2d\\u56fd"}'
    
    print json.dumps(b)
    {"name": "\u4e2d\u56fd"}
    
    chardet.detect(json.dumps(b))
    {'confidence': 1.0, 'encoding': 'ascii'}
    '中国' 中的ascii 字符码而不是真正的中文。想输出真正的中文需要指定ensure_ascii=False
    json.dumps(b,ensure_ascii=False)
    '{"name": "\xe6\x88\x91"}'
    
    print json.dumps(b,ensure_ascii=False) 
    {"name": "我"}
    
    chardet.detect(json.dumps(b,ensure_ascii=False))
    {'confidence': 0.7525, 'encoding': 'utf-8'}

    3. json.dump()
    把Python类型 以 字符串的形式 写到文件中

    import json
    a = [1,2,3,4]
    json.dump(a,open("digital.json","w"))
    b = {"name":"我"}
    json.dump(b,open("name.json","w"),ensure_ascii=False)
    json.dump(b,open("name2.json","w"),ensure_ascii=True)

    4. json.load()

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

    # -*- coding: utf-8 -*-
    import json
    number = json.load(open("digital.json"))
    print number
    b = json.load(open("name.json"))
    print b
    b.keys()
    print b['name']

    实战项目

    获取 lagou 城市表信息

    import urllib2
    import json
    import chardet
    
    url ='http://www.lagou.com/lbs/getAllCitySearchLabels.json?'
    request =urllib2.Request(url)
    response = urllib2.urlopen(request)
    print response.code
    resHtml = response.read()
    jsonobj = json.loads(resHtml)
    print type(jsonobj)
    print jsonobj
    
    citylist =[]
    
    allcitys = jsonobj['content']['data']['allCitySearchLabels']
    
    print allcitys.keys()
    
    for key in allcitys:
        print type(allcitys[key])
        for item in allcitys[key]:
            name =item['name'].encode('utf-8')
            print name,type(name)
            citylist.append(name)
    
    fp = open('city.json','w')
    
    content = json.dumps(citylist,ensure_ascii=False)
    print content
    
    fp.write(content)
    fp.close()
    输出


    JSONPath

    JSON 信息抽取类库从JSON文档中抽取指定信息的工具

    JSONPath与Xpath区别

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

    下载地址https://pypi.python.org/pypi/jsonpath/
    安装方法下载jsonpath解压之后执行'python setup.py install'

    参考文档

    XPath

    JSONPath

    Result

    /store/book/author

    $.store.book[*].author

    the authors of all books in the store

    //author

    $..author

    all authors

    /store/*

    $.store.*

    all things in store, which are some books and a red bicycle.

    /store//price

    $.store..price

    the price of everything in the store.

    //book[3]

    $..book[2]

    the third book

    //book[last()]

    $..book[(@.length-1)]
    $..book[-1:]

    the last book in order.

    //book[position()<3]

    $..book[0,1]
    $..book[:2]

    the first two books

    //book[isbn]

    $..book[?(@.isbn)]

    filter all books with isbn number

    //book[price<10]

    $..book[?(@.price<10)]

    filter all books cheapier than 10

    //*

    $..*

    all Elements in XML document. All members of JSON structure.

    案例

    还是以 http://www.lagou.com/lbs/getAllCitySearchLabels.json 为例获取所有城市

    import jsonpath
    import urllib2
    import chardet
    url ='http://www.lagou.com/lbs/getAllCitySearchLabels.json'
    request =urllib2.Request(url)
    response = urllib2.urlopen(request)
    print response.code
    resHtml = response.read()
    
    ##detect charset
    print chardet.detect(resHtml)
    
    jsonobj = json.loads(resHtml)
    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()

    XML

    xmltodict模块让使用XML感觉跟操作JSON一样
    Python操作XML的第三方库参考https://github.com/martinblech/xmltodict
    模块安装pip install xmltodict

    import xmltodict
    
    bookdict = xmltodict.parse("""
            
                
                      Harry Potter
                      29.99
                
                
                      Learning XML
                      39.95
                
        
        """)
    
    print bookdict.keys()
    [u'bookstore']
    print json.dumps(bookdict,indent=4)
    输出结果
    {
        "bookstore": {
            "book": [
                {
                    "title": {
                        "@lang": "eng", 
                        "#text": "Harry Potter"
                    }, 
                    "price": "29.99"
                }, 
                {
                    "title": {
                        "@lang": "eng", 
                        "#text": "Learning XML"
                    }, 
                    "price": "39.95"
                }
            ]
        }
    }

    数据提取总结

    HTML、XML

      XPath
      CSS选择器
      正则表达式

    JSON

      JSONPath
      转化成Python类型进行操作json类

    XML

      转化成Python类型xmltodict
      XPath
      CSS选择器
      正则表达式

    其他js、文本、电话号码、邮箱地址

      正则表达式

    • 精通网页抓取原理及技术精通正则表达式从结构化的和非结构化的数据中获取信息
    • XPATH、CSS选择器、正则表达式
    • 了解各种Web前端技术包括XHTML/XML/CSS/JavaScript/AJAX等
    • 对目标网站进行爬取分析找到最优化的爬取策略。
    json.loads的时候出错->要注意要解码的Json字符的编码


    1. 如果传入的字符串的编码是基于ASCII的而不是UTF-8的话需要指定字符编码参数cencoding
    对于dataDict = json.loads(dataJsonStr); 其中dataJsonStr是json字符串如果其编码本身是非UTF-8的话比如是GB2312的那么上述代码就会导致出错。改为对应的
    dataDict = json.loads(dataJsonStr, encoding="GB2312");
    2. 如果要解析的字符串本身的编码类型不是基于ASCII的那么调用json.loads之前需要先将对应字符串转换为Unicode类型的 还是以上述的
    dataDict = json.loads(dataJsonStr, encoding="GB2312");为例即使你此处的字符串dataJsonStr已经通过encoding指定了合适的编码但是由于其中包含了其他的编码的字符比如我本身dataJsonStr是GB2312的字符但是其中又包含了的一些日文字符此时json.loads还是会出错因为此处的dataJsonStr不是以ASCII为基础的字符编码所以需要先去将dataJsonStr转换为Unicode然后再调用json.loads就可以了。
    代码如下
    dataJsonStrUni = dataJsonStr.decode("GB2312"); 
    dataDict = json.loads(dataJsonStrUni, encoding="GB2312");

    encode和decode区别

    decode的作用是将其他编码的字符串转换成unicode编码。如str1.decode('gb2312')表示将gb2312编码的字符串str1转换成unicode编码。
    encode的作用是将unicode编码转换成其他编码的字符串。如str2.encode('gb2312')表示将unicode编码的字符串str2转换成gb2312编码。

    爬虫实践篇

    培养解决问题的思路、编码解码的理解

    解决问题的思路

    如何判断需求数据在哪
      A) 静态数据可通过查看网页源代码
      B) 定位具体哪一个url请求抓包在Fidder里面找怎么快速定位我要的数据呢(通过Body大小除了图片之外的Http请求)
    判断是什么请求方式Get还是Post
      在Composer raw 模拟发送数据
      A) 删除Header信息为什么删除代码简介美观、易于理解
      B) 如果做翻页最好拿第二三页做测试不要用首页因为有时候第二页是Post请求而第一是静态Get请求拿第二页测试的时候返回的是第一页容易错误还不自知
    参考案例Get、Post案例
    写python程序
    确认返回数据是什么格式的返回json还是html
      A) 那如果是json呢格式化数据应该做存储 B) 那如果是html呢提取数据使用XPath、CSS选择器、正则表达式

    Get和Post

    1. 右键查看源代码和 F12 Elements区别 右键查看源代码实质是一个Get请求 F12 Elements是整个页面 所有的请求url 加载完成的页面
    2. GET 和Post区别的方法 为什么拉钩用的Post不是表单提交密码原因是Post用户体验更好局部加载

    Urlencode

    urlencode()函数原理就是首先把中文字符转换为十六进制然后在每个字符前面加一个标识符%

    http://www.lagou.com/jobs/list_Python?px=default&city=%E5%8C%97%E4%BA%AC&district=%E6%9C%9D%E9%98%B3%E5%8C%BA&bizArea=%E6%9C%9B%E4%BA%AC#filterBox


    提出个问题中文字符按什么编码格式进行转化成十六进制呢    utf-8、gb2312、gbk urlencode编码

    utf-8与utf-8 urlencode区别

    import urllib
    country = u'中国'
    country.encode('utf-8')
    '\xe4\xb8\xad\xe5\x9b\xbd'
    urllib.quote(country.encode('utf-8'))
    '%E4%B8%AD%E5%9B%BD'

    gb2312与gb2312 urlencode区别

    import urllib
    country = u'中国'
    country.encode('gb2312')
    '\xd6\xd0\xb9\xfa'
    urllib.quote(country.encode('gb2312'))
    '%D6%D0%B9%FA'

    案例

    模拟出 拉勾网 如下url地址

    http://www.lagou.com/jobs/list_Python?px=default&city=%E5%8C%97%E4%BA%AC&district=%E6%9C%9D%E9%98%B3%E5%8C%BA&bizArea=%E6%9C%9B%E4%BA%AC#filterBox

    # -*- coding: utf-8 -*-
    import urllib
    import chardet
    
    city=u'北京'.encode('utf-8')
    district=u'朝阳区'.encode('utf-8')
    bizArea=u'望京'.encode('utf-8')
    
    query={
        'city':city,
        'district':district,
        'bizArea':bizArea
    }
    
    print chardet.detect(query['city'])
    {'confidence': 0.7525, 'encoding': 'utf-8'}
    
    print urllib.urlencode(query)
    city=%E5%8C%97%E4%BA%AC&bizArea=%E6%9C%9B%E4%BA%AC&district=%E6%9C%9B%E4%BA%AC
    
    print 'http://www.lagou.com/jobs/list_Python?px=default&'+urllib.urlencode(query)+'#filterBox'
    http://www.lagou.com/jobs/list_Python?px=default&city=%E5%8C%97%E4%BA%AC&bizArea=%E6%9C%9B%E4%BA%AC&district=%E6%9C%9B%E4%BA%AC#filterBox

    模拟出 阿里巴巴 如下url地址

    https://s.1688.com/selloffer/offer_search.htm?keywords=%CA%D6%BB%FA%BC%B0%C5%E4%BC%FE%CA%D0%B3%A1


    # -*- coding: utf-8 -*-
    import urllib
    import chardet
    
    keywords=u'手机及配件市场'.encode('gbk')
    
    query={
        'keywords':keywords,
    }
    print chardet.detect(query['keywords'])
    {'confidence': 0.99, 'encoding': 'GB2312'}
    print urllib.urlencode(query)
    keywords=%CA%D6%BB%FA%BC%B0%C5%E4%BC%FE%CA%D0%B3%A1
    
    print 'https://s.1688.com/selloffer/offer_search.htm?'+urllib.urlencode(query)
    https://s.1688.com/selloffer/offer_search.htm?keywords=%CA%D6%BB%FA%BC%B0%C5%E4%BC%FE%CA%D0%B3%A1

    模拟出 环球经贸网 如下url地址

    http://search.nowec.com/search?q=%B0%B2%C8%AB%C3%C5

    # -*- coding: utf-8 -*-
    import urllib
    import chardet
    
    q=u'安全门'.encode('gb2312')
    
    query={
        'q':q,
    }
    print chardet.detect(query['q'])
    {'confidence': 0.99, 'encoding': 'GB2312'}
    print urllib.urlencode(query)
    q=%B0%B2%C8%AB%C3%C5
    
    print 'http://search.nowec.com/search?'+urllib.urlencode(query)
    http://search.nowec.com/search?q=%B0%B2%C8%AB%C3%C5

    采集 百度贴吧 信息

    http://tieba.baidu.com/f?ie=utf-8&kw=%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB&fr=search

    解决问题思路
    1. 确认需求数据在哪。右键查看源代码
    2. Fidder模拟发送数据

    # -*- coding:utf-8 -*-
    import urllib2
    import urllib
    from lxml import etree
    import chardet
    import json
    import codecs
    
    
    def GetTimeByArticle(url):
        request = urllib2.Request(url)
        response = urllib2.urlopen(request)
        resHtml = response.read()
        html = etree.HTML(resHtml)
        time = html.xpath('//span[@class="tail-info"]')[1].text
        print time
        return time
    
    
    def main():
        output = codecs.open('tieba0812.json', 'w', encoding='utf-8')
    
        for pn in range(0, 250, 50):
    
            kw = u'网络爬虫'.encode('utf-8')
    
            url = 'http://tieba.baidu.com/f?kw=' + urllib.quote(kw) + '&ie=utf-8&pn=' + str(pn)
            print url
            request = urllib2.Request(url)
            response = urllib2.urlopen(request)
    
            resHtml = response.read()
            print resHtml
    
            html_dom = etree.HTML(resHtml)
            # print etree.tostring(html_dom)
            html = html_dom
    
            # site = html.xpath('//li[@data-field]')[0]
            for site in html.xpath('//li[@data-field]'):
                # print etree.tostring(site.xpath('.//a')[0])
                title = site.xpath('.//a')[0].text
                Article_url = site.xpath('.//a')[0].attrib['href']
                reply_date = GetTimeByArticle('http://tieba.baidu.com' + Article_url)
    
                jieshao = site.xpath('.//*[@class="threadlist_abs threadlist_abs_onlyline "]')[0].text.strip()
                author = site.xpath('.//*[@class="frs-author-name j_user_card "]')[0].text.strip()
                lastName = site.xpath('.//*[@class="frs-author-name j_user_card "]')[1].text.strip()
                print title, jieshao, Article_url, author, lastName
    
                item = {}
    
                item['title'] = title
                item['author'] = author
                item['lastName'] = lastName
                item['reply_date'] = reply_date
                print item
    
                line = json.dumps(item, ensure_ascii=False)
                print line
                print type(line)
    
                output.write(line + "\n")
            output.close()
        print 'end'
    
    if __name__ == '__main__':
        main()

    以 惠州市网上挂牌交易系统 

    为例http://www.hdgtjy.com/index/Index4/   采集所有的挂牌交易信息

    import urllib2
    import json
    
    fp = open('hdgtjy.json','w')
    for page in range(1,28):
    
        for i in range(5):
            try:
                send_headers = {'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded'}
    
                request =urllib2.Request('http://www.hdgtjy.com/Index/PublicResults',data='page='+ str(page) +'&size=10',headers=send_headers)
                response = urllib2.urlopen(request)
                data = response.read()
                obj =  json.loads(data)
                print obj['data'][0]['ADDRESS']
    
            except Exception,e:
                print e
        fp.write(data)
    fp.close()
    print 'end'
    查看运行结果感受一下。

    Requests基本用法与药品监督管理局

    Requests
    Requests 是唯一的一个非转基因的 Python HTTP 库人类可以安全享用
    urllib2urllib2是python自带的模块自定义 'Connection': 'keep-alive'通知服务器交互结束后不断开连接即所谓长连接。 当然这也是urllib2不支持keep-alive的解决办法之一另一个方法是Requests。

    安装 Requests

    优点Requests 继承了urllib2的所有特性。Requests支持HTTP连接保持和连接池支持使用cookie保持会话支持文件上传支持自动确定响应内容的编码支持国际化的 URL 和 POST 数据自动编码。
    缺陷requests不是python自带的库需要另外安装 easy_install or pip install。直接使用不能异步调用速度慢自动确定响应内容的编码。pip install requests

    文档http://cn.python-requests.org/zh_CN/latest/index.html    http://www.python-requests.org/en/master/#

    使用方法

    requests.get(url, data={'key1': 'value1'},headers={'User-agent','Mozilla/5.0'})
    requests.post(url, data={'key1': 'value1'},headers={'content-type': 'application/json'})

    以 药品监督管理局 为例http://app1.sfda.gov.cn/

    采集分类 国产药品商品名(6994) 下的所有的商品信息

    商品列表页http://app1.sfda.gov.cn/datasearch/face3/base.jsp?tableId=32&tableName=TABLE32&title=%B9%FA%B2%FA%D2%A9%C6%B7%C9%CC%C6%B7%C3%FB&bcId=124356639813072873644420336632

    商品详情页http://app1.sfda.gov.cn/datasearch/face3/content.jsp?tableId=32&tableName=TABLE32&tableView=%B9%FA%B2%FA%D2%A9%C6%B7%C9%CC%C6%B7%C3%FB&Id=211315

    # -*- coding: utf-8 -*-
    import urllib
    from lxml import etree
    import re
    import json
    import chardet
    import requests
    
    
    curstart = 2
    
    values = {
        'tableId': '32',
        'State': '1',
        'bcId': '124356639813072873644420336632',
        'State': '1',
        'tableName': 'TABLE32',
        'State': '1',
        'viewtitleName': 'COLUMN302',
        'State': '1',
        'viewsubTitleName': 'COLUMN299,COLUMN303',
        'State': '1',
        'curstart': str(curstart),
        'State': '1',
        'tableView': urllib.quote("国产药品商品名"),
        'State': '1',
    }
    
    post_headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
    }
    url = "http://app1.sfda.gov.cn/datasearch/face3/search.jsp"
    
    response = requests.post(url, data=values, headers=post_headers)
    
    resHtml = response.text
    print response.status_code
    # print resHtml
    
    Urls = re.findall(r'callbackC,\'(.*?)\',null', resHtml)
    for url in Urls:
        # 坑
        print url.encode('gb2312')
    查看运行结果感受一下。 


    总结

    1. User-Agent伪装Chrome欺骗web服务器
    2. urlencode 字典类型Dict、元祖 转化成 url query 字符串


    1. 完成商品详情页采集
    2. 完成整个项目的采集

    详情页

    # -*- coding: utf-8 -*-
    from lxml import etree
    import re
    import json
    import requests
    
    url ='http://app1.sfda.gov.cn/datasearch/face3/content.jsp?tableId=32&tableName=TABLE32&tableView=%B9%FA%B2%FA%D2%A9%C6%B7%C9%CC%C6%B7%C3%FB&Id=211315'
    get_headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36',
        'Connection': 'keep-alive',
    }
    item = {}
    response = requests.get(url,headers=get_headers)
    resHtml = response.text
    print response.encoding
    html = etree.HTML(resHtml)
    for site in html.xpath('//tr')[1:]:
        if len(site.xpath('./td'))!=2:
            continue
        name = site.xpath('./td')[0].text
        if not name:
            continue
        # value =site.xpath('./td')[1].text
        value = re.sub('<.*?>', '', etree.tostring(site.xpath('./td')[1],encoding='utf-8'))
        item[name.encode('utf-8')] = value
    
    json.dump(item,open('sfda.json','w'),ensure_ascii=False)
    完整项目

    # -*- coding: utf-8 -*-
    import urllib
    from lxml import etree
    import re
    import json
    import requests
    
    
    def ParseDetail(url):
        # url = 'http://app1.sfda.gov.cn/datasearch/face3/content.jsp?tableId=32&tableName=TABLE32&tableView=%B9%FA%B2%FA%D2%A9%C6%B7%C9%CC%C6%B7%C3%FB&Id=211315'
        get_headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36',
            'Connection': 'keep-alive',
        }
        item = {}
        response = requests.get(url, headers=get_headers)
        resHtml = response.text
        print response.encoding
        html = etree.HTML(resHtml)
        for site in html.xpath('//tr')[1:]:
            if len(site.xpath('./td')) != 2:
                continue
            name = site.xpath('./td')[0].text
            if not name:
                continue
            # value =site.xpath('./td')[1].text
            value = re.sub('<.*?>', '', etree.tostring(site.xpath('./td')[1], encoding='utf-8'))
            value = re.sub('', '', value)
            item[name.encode('utf-8').strip()] = value.strip()
    
        # json.dump(item, open('sfda.json', 'a'), ensure_ascii=False)
        fp = open('sfda.json', 'a')
        str = json.dumps(item, ensure_ascii=False)
        fp.write(str + '\n')
        fp.close()
    
    
    def main():
        curstart = 2
    
        values = {
            'tableId': '32',
            'State': '1',
            'bcId': '124356639813072873644420336632',
            'State': '1',
            'tableName': 'TABLE32',
            'State': '1',
            'viewtitleName': 'COLUMN302',
            'State': '1',
            'viewsubTitleName': 'COLUMN299,COLUMN303',
            'State': '1',
            'curstart': str(curstart),
            'State': '1',
            'tableView': urllib.quote("国产药品商品名"),
            'State': '1',
        }
    
        post_headers = {
            'Content-Type': 'application/x-www-form-urlencoded',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
        }
        url = "http://app1.sfda.gov.cn/datasearch/face3/search.jsp"
    
        response = requests.post(url, data=values, headers=post_headers)
    
        resHtml = response.text
        print response.status_code
        # print resHtml
    
        Urls = re.findall(r'callbackC,\'(.*?)\',null', resHtml)
        for url in Urls:
            # 坑
            url = re.sub('tableView=.*?&', 'tableView=' + urllib.quote("国产药品商品名") + "&", url)
            ParseDetail('http://app1.sfda.gov.cn/datasearch/face3/' + url.encode('gb2312'))
    
    
    if __name__ == '__main__':
        main()

    拉钩招聘网

    以拉钩具体详情页为例进行抓取。http://www.lagou.com/jobs/2101463.html

    from lxml import etree
    import requests
    import re
    
    response = requests.get('http://www.lagou.com/jobs/2101463.html')
    resHtml = response.text
    
    html = etree.HTML(resHtml)
    
    title = html.xpath('//h1[@title]')[0].attrib['title']
    #salary= html.xpath('//span[@class="red"]')[0].text
    
    salary = html.xpath('//dd[@class="job_request"]/p/span')[0].text
    worklocation = html.xpath('//dd[@class="job_request"]/p/span')[1].text
    experience = html.xpath('//dd[@class="job_request"]/p/span')[2].text
    education = html.xpath('//dd[@class="job_request"]/p/span')[3].text
    worktype = html.xpath('//dd[@class="job_request"]/p/span')[4].text
    Temptation = html.xpath('//dd[@class="job_request"]/p[2]')[0].text
    
    print salary,worklocation,experience,education,worktype,Temptation
    
    description_tag = html.xpath('//dd[@class="job_bt"]')[0]
    description =  etree.tostring( description_tag,encoding='utf-8')
    #print description
    deal_descp =  re.sub('<.*?>','',description)
    print deal_descp.strip()
    publisher_name =  html.xpath('//*[@class="publisher_name"]//@title')[0]
    pos =  html.xpath('//*[@class="pos"]')[0].text
    chuli_lv =  html.xpath('//*[@class="data"]')[0].text
    chuli_yongshi =  html.xpath('//*[@class="data"]')[1].text
    
    print chuli_lv,chuli_yongshi,pos,publisher_name

    爬取糗事百科段子

    确定URL并抓取页面代码,首先我们确定好页面的URL是 http://www.qiushibaike.com/8hr/page/4, 其中最后一个数字1代表页数我们可以传入不同的值来获得某一页的段子内容。我们初步构建如下的代码来打印页面代码内容试试看先构造最基本的页面抓取方式看看会不会成功。在Composer raw 模拟发送数据

    GET http://www.qiushibaike.com/8hr/page/2/ HTTP/1.1
    Host: www.qiushibaike.com
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
    Accept-Language: zh-CN,zh;q=0.8
    在删除了User-Agent、Accept-Language报错。应该是headers验证的问题加上一个headers验证试试看

    # -*- coding:utf-8 -*-
    import urllib
    import requests
    import re
    import chardet
    from lxml import etree
    
    page = 2
    url = 'http://www.qiushibaike.com/8hr/page/' + str(page) + "/"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36',
        'Accept-Language': 'zh-CN,zh;q=0.8'}
    try:
        response = requests.get(url, headers=headers)
        resHtml = response.text
    
        html = etree.HTML(resHtml)
        result = html.xpath('//div[contains(@id,"qiushi_tag")]')
        for site in result:
            #print etree.tostring(site,encoding='utf-8')
    
            item = {}
            imgUrl = site.xpath('./div/a/img/@src')[0].encode('utf-8')
            username = site.xpath('./div/a/@title')[0].encode('utf-8')
            #username = site.xpath('.//h2')[0].text
            content = site.xpath('.//div[@class="content"]')[0].text.strip().encode('utf-8')
            vote = site.xpath('.//i')[0].text
            #print site.xpath('.//*[@class="number"]')[0].text
            comments = site.xpath('.//i')[1].text
    
            print imgUrl, username, content, vote, comments
    
    except Exception, e:
        print e




    多线程爬虫实战糗事百科

    python下多线程的思考

    Queue是python中的标准库可以直接import Queue引用; 队列是线程间最常用的交换数据的形式。对于共享资源加锁是个重要的环节。因为python原生的list,dict等都是not thread safe的。而Queue是线程安全的因此在满足使用条件下建议使用队列。

    Python Queue模块有三种队列及构造函数:

    1、Python Queue模块的FIFO队列先进先出。 class Queue.Queue(maxsize)
    2、LIFO类似于堆即先进后出。 class Queue.LifoQueue(maxsize)
    3、还有一种是优先级队列级别越低越先出来。 class Queue.PriorityQueue(maxsize)

    Queue队列对象

    初始化 class Queue.Queue(maxsize) FIFO 先进先出

    包中的常用方法:

    Queue.qsize() 返回队列的大小
    Queue.empty() 如果队列为空返回True,反之False
    Queue.full() 如果队列满了返回True,反之False
    Queue.full 与 maxsize 大小对应
    Queue.get([block[, timeout]])获取队列timeout等待时间
    调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block默认为True。
    如果队列为空且block为Trueget()就使调用线程暂停直至有项目可用。
    如果队列为空且block为False队列将引发Empty异常。

    创建一个“队列”对象
    import Queue myqueue = Queue.Queue(maxsize = 10)
    将一个值放入队列中
    myqueue.put(10)
    将一个值从队列中取出
    myqueue.get()


    # -*- coding:utf-8 -*-
    import requests
    from lxml import etree
    from Queue import Queue
    import threading
    import time
    import json
    
    
    class thread_crawl(threading.Thread):
        '''
        抓取线程类
        '''
    
        def __init__(self, threadID, q):
            threading.Thread.__init__(self)
            self.threadID = threadID
            self.q = q
    
        def run(self):
            print "Starting " + self.threadID
            self.qiushi_spider()
            print "Exiting ", self.threadID
    
        def qiushi_spider(self):
            # page = 1
            while True:
                if self.q.empty():
                    break
                else:
                    page = self.q.get()
                    print 'qiushi_spider=', self.threadID, ',page=', str(page)
                    url = 'http://www.qiushibaike.com/hot/page/' + str(page) + '/'
                    headers = {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36',
                        'Accept-Language': 'zh-CN,zh;q=0.8'}
                    # 多次尝试失败结束、防止死循环
                    timeout = 4
                    while timeout > 0:
                        timeout -= 1
                        try:
                            content = requests.get(url, headers=headers)
                            data_queue.put(content.text)
                            break
                        except Exception, e:
                            print 'qiushi_spider', e
                    if timeout < 0:
                        print 'timeout', url
    
    
    class Thread_Parser(threading.Thread):
        '''
        页面解析类
        '''
    
        def __init__(self, threadID, queue, lock, f):
            threading.Thread.__init__(self)
            self.threadID = threadID
            self.queue = queue
            self.lock = lock
            self.f = f
    
        def run(self):
            print 'starting ', self.threadID
            global total, exitFlag_Parser
            while not exitFlag_Parser:
                try:
                    '''
                    调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block默认为True。
                    如果队列为空且block为Trueget()就使调用线程暂停直至有项目可用。
                    如果队列为空且block为False队列将引发Empty异常。
                    '''
                    item = self.queue.get(False)
                    if not item:
                        pass
                    self.parse_data(item)
                    self.queue.task_done()
                    print 'Thread_Parser=', self.threadID, ',total=', total
                except:
                    pass
            print 'Exiting ', self.threadID
    
        def parse_data(self, item):
            '''
            解析网页函数
            :param item: 网页内容
            :return:
            '''
            global total
            try:
                html = etree.HTML(item)
                result = html.xpath('//div[contains(@id,"qiushi_tag")]')
                for site in result:
                    try:
                        imgUrl = site.xpath('.//img/@src')[0]
                        title = site.xpath('.//h2')[0].text
                        content = site.xpath('.//div[@class="content"]')[0].text.strip()
                        vote = None
                        comments = None
                        try:
                            vote = site.xpath('.//i')[0].text
                            comments = site.xpath('.//i')[1].text
                        except:
                            pass
                        result = {
                            'imgUrl': imgUrl,
                            'title': title,
                            'content': content,
                            'vote': vote,
                            'comments': comments,
                        }
    
                        with self.lock:
                            # print 'write %s' % json.dumps(result)
                            self.f.write(json.dumps(result, ensure_ascii=False).encode('utf-8') + "\n")
    
                    except Exception, e:
                        print 'site in result', e
            except Exception, e:
                print 'parse_data', e
            with self.lock:
                total += 1
    
    data_queue = Queue()
    exitFlag_Parser = False
    lock = threading.Lock()
    total = 0
    
    def main():
        output = open('qiushibaike.json', 'a')
    
        #初始化网页页码page从1-10个页面
        pageQueue = Queue(50)
        for page in range(1, 11):
            pageQueue.put(page)
    
        #初始化采集线程
        crawlthreads = []
        crawlList = ["crawl-1", "crawl-2", "crawl-3"]
    
        for threadID in crawlList:
            thread = thread_crawl(threadID, pageQueue)
            thread.start()
            crawlthreads.append(thread)
    
        #初始化解析线程parserList
        parserthreads = []
        parserList = ["parser-1", "parser-2", "parser-3"]
        #分别启动parserList
        for threadID in parserList:
            thread = Thread_Parser(threadID, data_queue, lock, output)
            thread.start()
            parserthreads.append(thread)
    
        # 等待队列清空
        while not pageQueue.empty():
            pass
    
        # 等待所有线程完成
        for t in crawlthreads:
            t.join()
    
        while not data_queue.empty():
            pass
        # 通知线程是时候退出
        global exitFlag_Parser
        exitFlag_Parser = True
    
        for t in parserthreads:
            t.join()
        print "Exiting Main Thread"
        with lock:
            output.close()
    
    
    if __name__ == '__main__':
        main()

    入坑-乱码

    关于爬虫乱码有很多各式各样的问题这里不仅是中文乱码编码转换、还包括一些如日文、韩文 、俄文、藏文之类的乱码处理因为解决方式是一致的故在此统一说明。

    网络爬虫出现乱码的原因源网页编码和爬取下来后的编码格式不一致。如源网页为gbk编码的字节流而我们抓取下后程序直接使用utf-8进行编码并输出到存储文件中这必然会引起乱码 即当源网页编码和抓取下来后程序直接使用处理编码一致时则不会出现乱码; 此时再进行统一的字符编码也就不会出现乱码了

    注意区分源网页的编码A、程序直接使用的编码B、统一转换字符的编码C。

    乱码的解决方法

    确定源网页的编码A,编码A往往在网页中的三个位置

    1. http header的Content-Type

    获取服务器 header 的站点可以通过它来告知浏览器一些页面内容的相关信息。 Content-Type 这一条目的写法就是 "text/html; charset=utf-8"。


    2. meta charset

    3. 网页头中Document定义

    
                        
                        
  • 你可能感兴趣的:(爬虫入门)