爬虫精简

案例:

爬虫>>非结构化数据>>2.6案例,黑马代码

多线程:参考千峰代码

 

盲区:

1.在用fidder抓取会话时,怎么找对应页面的url,另外如果是post请求,data数据在哪里看,以及代码中要添加哪些数据?

拓展了解:

HTTPPasswordMgrWithDefaultRealm()

ProxyBasicAuthHandler(代理授权验证)

2,爬取返回后对象的数据形式以及正则匹配后返回的数据类型

3,编码和字符集的关系,数据类型和对象类型间的关系,编码方式和数据类型间的关系

4,爬取第一层链接后,如何以及为什么要进入第二层链接。在scrapy框架中,spider文件下的多页url中的回调函数运用的逻辑原理

5,crapy框架中的request和response分别是在何时产生和应用的

主要是理解逻辑

1,工具(略)

2,原理:

请求得到

简单请求:

包urllib,

urllib.open(url) >>请求网站 >>返回类文件对象 >>read()读取下来,变成文件 (整个就是一个爬虫)

构造一个request对象目的添加HTTP报头(参数1:url,参数2:headers(伪造浏览器)) >>urllib.open(request) >>请求网站 >>返回类文件对象 >>读取下来,变成文件 (整个就是一个爬虫)

扩展(User-Agent可以随机,建个列表,然后random.choice随机从列表选一个)

 

get和post传参:

word = {"wd" : "传智播客"}
urllib.urlencode(word)
# 通过urllib.urlencode()方法,将字典键值对按URL编码转换,从而能被web服务器接受。
print urllib.unquote("wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2")
wd=传智播客
# 通过urllib.unquote()方法,把 URL编码字符串,转换回原先字符串。

 

有些网页内容使用AJAX加载,只要记得,AJAX一般返回的是JSON,直接对AJAX地址进行post或get,就返回JSON数据了

在访问的时候则会报出SSLError

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

 

 

2,基本的urlopen()方法不支持代理、cookie等其他的HTTP/HTTPS高级功能

所以可以自定义opener

# 构建一个HTTPHandler 处理器对象,支持处理HTTPS请求
# http_handler = urllib2.HTTPSHandler()
opener = urllib2.build_opener(http_handler)
response = opener.open(request)

 

ProxyHandler处理器(*)

# 构建了两个代理Handler,一个有代理IP,一个没有代理IP
httpproxy_handler = urllib2.ProxyHandler({"http" : "124.88.67.81:80"})
nullproxy_handler = urllib2.ProxyHandler({})
opener = urllib2.build_opener(nullproxy_handler)

是先创建opener,里面可以加参数,然后用opener.open()函数(里面携带了一些参数)来打开request

代理IP,也可以用列表的形式创建,然后random.choice()来随机选择,并将其加入到opener中,先handler再build_opener(handler)

 

cookie登录(*)(多看视频,然后与黑马教程做对比)

1,直接登录,取到cookie(黑马说容易被封)

2,利用cookielib包,一次请求保存下来

 

 

URLError 产生的原因主要有:

  1. 没有网络连接

  2. 服务器连接失败

  3. 找不到指定的服务器

HTTP Error,错误代号是404,错误原因是Not Found,说明服务器无法找到被请求的页面。

通常产生这种错误的,要么url不对,要么ip被封。

根据返回的错误码对应的去百度(400-599)

 

包requests

对urllib进行了封装

response = requests.get("http://www.baidu.com/s?", params = kw, headers = headers)
# 查看响应内容,response.text 返回的是Unicode格式的数据
print response.text
# 查看响应内容,response.content返回的字节流数据
​
# 查看响应内容,response.content返回的字节流数据
response = requests.post("http://www.baidu.com/", data = data)
params是get请求,data是post请求
代理参数proxies
response = requests.get("http://www.baidu.com", proxies = proxies)
私密代理
import requests

# 如果代理需要使用HTTP Basic Auth,可以使用下面这种格式:
proxy = { "http": "mr_mao_hacker:[email protected]:16816" }

response = requests.get("http://www.baidu.com", proxies = proxy)

print response.text
web客户端验证
如果是Web客户端验证,需要添加 auth = (账户名, 密码)

import requests

auth=('test', '123456')

response = requests.get('http://192.168.199.107', auth = auth)

print response.text

Cookies 和 Sission:如果一个响应中包含了cookie,那么我们可以利用 cookies参数拿到:

实现人人网登录,带cookie
import requests

# 1. 创建session对象,可以保存Cookie值
ssion = requests.session()

# 2. 处理 headers
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}

# 3. 需要登录的用户名和密码
data = {"email":"[email protected]", "password":"alarmchime"}  

# 4. 发送附带用户名和密码的请求,并获取登录后的Cookie值,保存在ssion里
ssion.post("http://www.renren.com/PLogin.do", data = data)

# 5. ssion包含用户登录后的Cookie值,可以直接访问那些登录后才可以访问的页面
response = ssion.get("http://www.renren.com/410043129/profile")
处理HTTPS请求 SSL证书验证
Requests也可以为HTTPS请求验证SSL证书:

要想检查某个主机的SSL证书,你可以使用 verify 参数(也可以不写)
import requests
response = requests.get("https://www.baidu.com/", verify=True)

# 也可以省略不写
# response = requests.get("https://www.baidu.com/")
print r.text	
如果我们想跳过 12306 的证书验证,把 verify 设置为 False 就可以正常请求了。

r = requests.get("https://www.12306.cn/mormhweb/", verify = False)

解析和数据提取

  • 非结构化数据:先有数据,再有结构,

  • 结构化数据:先有结构、再有数据

  • 不同类型的数据,我们需要采用不同的方式来处理。

非结构化的数据处理

文本、电话号码、邮箱地址

  • 正则表达式

HTML 文件

  • 正则表达式

  • XPath

  • CSS选择器


结构化的数据处理

JSON 文件

  • JSON Path

  • 转化成Python类型进行操作(json类)

XML 文件

  • 转化成Python类型(xmltodict)

  • XPath

  • CSS选择器

  • 正则表达式

 

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

  1. 明确目标 (要知道你准备在哪个范围或者网站去搜索)

  2. 爬 (将所有的网站的内容全部爬下来)

  3. 取 (去掉对我们没用处的数据)

  4. 处理数据(按照我们想要的方式存储和使用)

     

正则表达式

Pattern 对象的一些常用方法主要有:

  • match 方法:从起始位置开始查找,一次匹配

  • search 方法:从任何位置开始查找,一次匹配

  • findall 方法:全部匹配,返回列表

  • finditer 方法:全部匹配,返回迭代器

  • split 方法:分割字符串,返回列表

  • sub 方法:替换

 

match方法:

match(string[, pos[, endpos]],pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。

>>> m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配,正好匹配
>>> print m                                         # 返回一个 Match 对象
<_sre.SRE_Match object at 0x10a42aac0>

>>> m.group(0)   # 可省略 0
'12'
>>> m.start(0)   # 可省略 0
3
>>> m.end(0)     # 可省略 0
5
>>> m.span(0)    # 可省略 0
(3, 5)

group()=group(0)[用于获得一个或多个分组匹配的字符串]

span(star(group),end(group))[end(group)子串最后一个索引+1]

注意,当正则表达式的元素是有多个()小括号时,每一个小括号对应一个group索引

pattern = re.compile(r'([a-z]+) ([a-z]+)', re.I)  # re.I 表示忽略大小写
>>> m = pattern.match('Hello World Wide Web')
>>> m.group(0)  # 返回匹配成功的整个子串
'Hello World'
>>> m.group(1)  # 返回第一个分组匹配成功的子串
'Hello'
>>> m.group(2)  # 返回第二个分组匹配成功的子串
'World'
>>> m.groups()  # 等价于 (m.group(1), m.group(2), ...)
('Hello', 'World')

(*)

re.I表示忽略大小写

re.S视为单行模式(忽略\n)

re.M视为多行模式

 

search方法:

search(string[, pos[, endpos]],pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)

search 方法用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果,

 

findall方法:

findall(string[, pos[, endpos]])

其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。

 

finditer方法:

finditer 方法的行为跟 findall 的行为类似,也是搜索整个字符串,获得所有匹配的结果。但它返回一个顺序访问每一个匹配结果(Match 对象)的迭代器。


 

split方法:

split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:

split(string[, maxsplit])

其中,maxsplit 用于指定最大分割次数,不指定将全部分割。

import re
p = re.compile(r'[\s\,\;]+')
print p.split('a,b;; c   d')
》》》['a', 'b', 'c', 'd']

 

sub方法:

sub(repl, string[, count])

  • count 用于指定最多替换次数,不指定时全部替换。

import re
p = re.compile(r'(\w+) (\w+)') # \w = [A-Za-z0-9]
s = 'hello 123, hello 456'

print p.sub(r'hello world', s)  # 使用 'hello world' 替换 'hello 123' 和 'hello 456'
print p.sub(r'\2 \1', s)        # 引用分组

def func(m):
    return 'hi' + ' ' + m.group(2)

print p.sub(func, s)
print p.sub(func, s, 1)         # 最多替换一次
执行结果:

hello world, hello world
123 hello, 456 hello
hi 123, hi 456
hi 123, hello 456

匹配中文:

在某些情况下,我们想匹配文本中的汉字,有一点需要注意的是,中文的 unicode 编码范围 主要在 [u4e00-u9fa5],这里说主要是因为这个范围并不完整,比如没有包括全角(中文)标点,不过,在大部分情况下,应该是够用的。

假设现在想把字符串 title = u'你好,hello,世界' 中的中文提取出来,可以这么做:

import re

title = u'你好,hello,世界'
pattern = re.compile(ur'[\u4e00-\u9fa5]+')
result = pattern.findall(title)

print result

注意到,我们在正则表达式前面加上了两个前缀 ur,其中 r 表示使用原始字符串,u 表示是 unicode 字符串。

执行结果:

[u'\u4f60\u597d', u'\u4e16\u754c']

 

贪婪模式与非贪婪模式

  1. 贪婪模式:在整个表达式匹配成功的前提下,尽可能多的匹配 ( * );

  2. 非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配 ( ? );

  3. Python里数量词默认是贪婪的。

     

示例一 : 源字符串:abbbc

  • 使用贪婪的数量词的正则表达式

     

    ab*

     

    ,匹配结果: abbb。

    * 决定了尽可能多匹配 b,所以a后面所有的 b 都出现了。

  • 使用非贪婪的数量词的正则表达式

    ab*?

    ,匹配结果: a。

    即使前面有 *,但是 ? 决定了尽可能少匹配 b,所以没有 b。

 

 

 

 

Xpath;

有同学说,我正则用的不好,处理HTML文档很累,有没有其他的方法?

有!那就是XPath,我们可以先将 HTML文件 转换成 XML文档,然后用 XPath 查找 HTML 节点或元素。

 

  • XML 是一种标记语言,很类似 HTML

  • XML 的设计宗旨是传输数据,而非显示数据

父元素、子、同胞、先辈、后代

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

xpath工具

Chrome插件 XPath Helper

xpath 表达式(*)

//,/,. , .. ,@

 

lxml库

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

xml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用之前学习的XPath语法,来快速的定位特定元素以及节点信息。

from lxml import etree 
利用etree.HTML,将字符串解析为HTML文档
html = etree.HTML(text) 
# 按字符串序列化HTML文档
result = etree.tostring(html)

xpath匹配到的是元素的集合(类似[, , , , ]),返回的是列表,里面每一个元素都是元素的集合

区分://title[@lang]和//title/lang的区别,前者选取所有拥有名为 lang 的属性的 title 元素,后者选取title标签的lang属性

 

etree.parse的作用

from lxml import etree

html = etree.parse('hello.html')
result = html.xpath('//li/a[@href="link1.html"]')

print result

 

 

beautifulsoup:

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

#创建 Beautiful Soup 对象
soup = BeautifulSoup(html)

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:

  • Tag

  • NavigableString

  • BeautifulSoup

  • Comment

  1. Tag

Tag 通俗点讲就是 HTML 中的一个个标签,对于 Tag,它有两个重要的属性,是 name 和 attrs

print soup.p.attrs
# {'class': ['title'], 'name': 'dromouse'}
# 在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。
  1. NavigableString

既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?很简单,用 .string 即可,例如

 

 

css选择器;用到的方法是 soup.select()

(1)通过标签名查找print soup.select('title')

,(2)通过标类名查找,print soup.select('.sister')

(3)通过id名查找,print soup.select('#link1')

(4)组合查找,组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开

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

直接子标签查找,则使用 > 分隔

print soup.select("head > title")
#[The Dormouse's story

(5)属性查找

查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到

(6)获取内容

以上的 select 方法返回的结果都是列表形式,可以遍历形式输出,然后用 get_text() 方法来获取它的内容。

print soup.select('title')[0].get_text()

 

 

 

数据提取之JSON和JsonPATH

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

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

  1. json.loads()

把Json格式字符串解码转换成Python对象 从json到python的类型转化对照如下:

  1. json.dumps()

实现python类型转化为json字符串,返回一个str对象 把一个Python对象编码转换成Json字符串

从python原始类型向json类型的转化对照如下:

添加参数 ensure_ascii=False 禁用ascii编码,按utf-8编码
  1. json.dump()

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

 

  1. json.load()

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

 

jsonpath

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

(html - re, xml- xpath , xml- bs4 , jsonpath - json)

# 把json格式字符串转换成python对象
jsonobj = json.loads(html)

# 从根节点开始,匹配name节点
citylist = jsonpath.jsonpath(jsonobj,'$..name')

 

字符串编码转换(*)

##字符串编码转换

这是中国程序员最苦逼的地方,什么乱码之类的几乎都是由汉字引起的。
其实编码问题很好搞定,只要记住一点:

####任何平台的任何编码 都能和 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字符集进行编码的一种编码方式

 

队列对象Queue

队列是线程间最常用的交换数据的形式

 

反爬虫技术:

selenium +phanotomJs

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

使用zip()函数来可以把列表合并,并创建一个元组对的列表[(1,2), (3,4)]

黑马很多案例:ajax,机器识别、动态html

 

通常防止爬虫被反主要有以下几个策略:

  • 动态设置User-Agent(随机切换User-Agent,模拟不同用户的浏览器信息)

  • 禁用Cookies(也就是不启用cookies middleware,不向Server发送cookies,有些网站通过cookie的使用发现爬虫行为)

    • 可以通过COOKIES_ENABLED 控制 CookiesMiddleware 开启或关闭

  • 设置延迟下载(防止访问过于频繁,设置为 2秒 或更高)

  • Google Cache 和 Baidu Cache:如果可能的话,使用谷歌/百度等搜索引擎服务器页面缓存获取页面数据。

  • 使用IP地址池:VPN和代理IP,现在大部分网站都是根据IP来ban的。

  • 使用 Crawlera(专用于爬虫的代理组件),正确配置和设置下载中间件后,项目所有的request都是通过crawlera发出。

 

Scrapy框架

  • 创建一个Scrapy项目

  • 定义提取的结构化数据(Item)

  • 编写爬取网站的 Spider 并提取出结构化数据(Item)

  • 编写 Item Pipelines 来存储提取到的Item(即结构化数据)

1,明确目标:建类,类似orm的映射‘

2,爬虫编写:三属性,一函数,函数解析parse(*)[并返回item]

3,保存item实例,可以用yield,parse_item

extract()方法返回 Unicode字符

 

 

scrapy shell

以后做数据提取的时候,可以把现在Scrapy Shell中测试,测试通过后再应用到代码中

 

pipelines

当Item在Spider中被收集之后,它将会被传递到Item Pipeline,这些Item Pipeline组件按定义的顺序处理Item。

定义pipelines文件时,依然定义的类,parse_item固定,且记得传参

 

  • start_requests(self) (*)

    该方法必须返回一个可迭代对象(iterable)。该对象包含了spider用于爬取(默认实现是使用 start_urls 的url)的第一个Request。

    当spider启动爬取并且未指定start_urls时,该方法被调用。

  • parse(self, response)

    当请求url返回网页没有指定回调函数时,默认的Request对象回调函数。用来处理网页返回的response,以及生成Item或者Request对象

 

 

请思考 parse()方法的工作机制(*):

1. 因为使用的yield,而不是return。parse函数将会被当做一个生成器使用。scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型;
2. 如果是request则加入爬取队列,如果是item类型则使用pipeline处理,其他类型则返回错误信息。
3. scrapy取到第一部分的request不会立马就去发送这个request,只是把这个request放到队列里,然后接着从生成器里获取;
4. 取尽第一部分的request,然后再获取第二部分的item,取到item了,就会放到对应的pipeline里处理;
5. parse()方法作为回调函数(callback)赋值给了Request,指定parse()方法来处理这些请求 scrapy.Request(url, callback=self.parse)
6. Request对象经过调度,执行生成 scrapy.http.response()的响应对象,并送回给parse()方法,直到调度器中没有Request(递归的思路)
7. 取尽之后,parse()工作结束,引擎再根据队列和pipelines中的内容去执行相应的操作;
8. 程序在取得各个页面的items前,会先处理完之前所有的request队列里的请求,然后再提取items。
7. 这一切的一切,Scrapy引擎和调度器将负责到底。

 

 

CrawlSpiders

创建模板:

scrapy genspider -t crawl tencent tencent.com

它是Spider的派生类,Spider类的设计原则是只爬取start_url列表中的网页,而CrawlSpider类定义了一些规则(rule)来提供跟进link的方便的机制,从爬取的网页中获取link并继续爬取的工作更适合。

LinkExtractors,Link Extractors 的目的很简单: 提取链接

每个LinkExtractor有唯一的公共方法是 extract_links(),它接收一个 Response 对象,并返回一个 scrapy.link.Link 对象。

Link Extractors要实例化一次,并且 extract_links 方法会根据不同的 response 调用多次提取链接。

#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接
page_lx = LinkExtractor(allow = ('start=\d+'))
​
rules = [
    #提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)
    Rule(page_lx, callback = '定义解析函数【千万不能是parse,否则会覆盖报错】', follow = True)
]

你可能感兴趣的:(笔记,Python)