爬虫>>非结构化数据>>2.6案例,黑马代码
多线程:参考千峰代码
1.在用fidder抓取会话时,怎么找对应页面的url,另外如果是post请求,data数据在哪里看,以及代码中要添加哪些数据?
拓展了解:
HTTPPasswordMgrWithDefaultRealm()
ProxyBasicAuthHandler(代理授权验证)
2,爬取返回后对象的数据形式以及正则匹配后返回的数据类型
3,编码和字符集的关系,数据类型和对象类型间的关系,编码方式和数据类型间的关系
4,爬取第一层链接后,如何以及为什么要进入第二层链接。在scrapy框架中,spider文件下的多页url中的回调函数运用的逻辑原理
5,crapy框架中的request和response分别是在何时产生和应用的
主要是理解逻辑
1,工具(略)
2,原理:
简单请求:
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 产生的原因主要有:
没有网络连接
服务器连接失败
找不到指定的服务器
HTTP Error,错误代号是404,错误原因是Not Found,说明服务器无法找到被请求的页面。
通常产生这种错误的,要么url不对,要么ip被封。
根据返回的错误码对应的去百度(400-599)
对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选择器
正则表达式
实际上爬虫一共就四个主要步骤:
明确目标 (要知道你准备在哪个范围或者网站去搜索)
爬 (将所有的网站的内容全部爬下来)
取 (去掉对我们没用处的数据)
处理数据(按照我们想要的方式存储和使用)
正则表达式
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']
贪婪模式与非贪婪模式
贪婪模式:在整个表达式匹配成功的前提下,尽可能多的匹配 ( * );
非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配 ( ? );
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
Tag
Tag 通俗点讲就是 HTML 中的一个个标签,对于 Tag,它有两个重要的属性,是 name 和 attrs
print soup.p.attrs # {'class': ['title'], 'name': 'dromouse'} # 在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。
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模块提供了四个功能:dumps
、dump
、loads
、load
,用于字符串 和 python数据类型间进行转换
json.loads()
把Json格式字符串解码转换成Python对象 从json到python的类型转化对照如下:
json.dumps()
实现python类型转化为json字符串,返回一个str对象 把一个Python对象编码转换成Json字符串
从python原始类型向json类型的转化对照如下:
添加参数 ensure_ascii=False 禁用ascii编码,按utf-8编码
json.dump()
将Python内置类型序列化为json对象后写入文件
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项目
定义提取的结构化数据(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对象
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引擎和调度器将负责到底。
创建模板:
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) ]