爬虫概念
数据获取的方式:
- 企业生产的用户数据:大型互联网公司有海量用户,所以他们积累数据有天然优势。有数据意识的中小型企业,也开始积累的数据。
- 数据管理咨询公司
- 政府/机构提供的公开数据
- 第三方数据平台购买数据
- 爬虫爬取数据
什么是爬虫
抓去网页数据的程序
网页三大特征:
- 每个网页都有自己的
URL
- 网页都使用
HTML
标记语言来描述页面信息 - 网页都使用
HTTP/HTTPS
协议来传输HTML
数据
爬虫的设计思路
- 确定需要爬取的网页
URL
地址 - 通过
HTTP/HTTPS
协议来获取对应的HTML
页面 - 提取
HTML
页面中的数据
如果是需要的数据,就保存起来
如果页面是其它URL
,那就继续爬取
如何抓取HTML页面
HTTP协议请求的处理,urllib
, urllib2
, requests
,处理后的请求可以模拟浏览器发送请求,获取服务器响应的文件
解析服务器响应的内容re
, xpath
(常用), BeautifulSoup4(bs4)
, jsonpath
, pyquery
等使用某种描述性一样来给需要提取的数据定义一个匹配规则,符合这个规则的数据就会被匹配。
如何采集动态HTML,验证码的处理Selenium
(自动化测试工具) + PhantomJS
(无界面浏览器)
验证码处理通过Tesseract
: 机器图像识别系统(图片中的文本识别)
Scrapy框架
(Scrapy
, Pyspider
)
- 高性能高定制型(异步网络框架
twisted
),所以数据下载速度快 - 提供了数据存储,数据下载,提取规则等组件
分布式策略
- 是否有那么多的机器去做分布式?
- 获取的数据是否值得搭建分布式系统?
使用scrapy-redis
来搭建,在Scrapy
的基础上添加了一套 Redis
数据库为核心的一套组件,让Scrapy
框架支持分布式的功能。主要在Redis
中做请求指纹去重,请求分配,数据临时存储
爬虫 – 反爬虫 – 反反爬虫
反爬虫: User-Agent
, IP
, 代理
, 验证码
, 动态数据加载
, 加密数据
数据的价值,是否值得去费劲去做反爬虫,一般做到代理阶段或封IP
。
机器成本 + 人力成本 > 数据价值
爬虫和反爬虫之间的斗争,最后一定是爬虫获胜。
只要是真实用户可以浏览的网页数据,爬虫就一定能爬下来。(爬虫模拟浏览器获取数据)
通用爬虫
搜索引擎使用的爬虫系统
目标:尽可能把互联网上所有的网页下载下来,放到本地服务器里形成备份,再对这些网页做相关处理(提取关键字,去掉广告),最后提供一个用户检索接口
抓取流程:
- 首先选取一部分已有的
URL
,把这些URL
放到待爬取队列。 - 从队列里去取出这些
URL
,然后解析DNS
得到主机IP
,然后去这个IP
对应的服务器下载HTML
页面,保存到搜索引擎的本地服务器里,之后把这个已经爬过的URL
放入到已经爬取队列中 - 分析网页内容,找出网页中的其它
URL
内容,继续爬取。
搜索引擎如何获取一个新网站的URL
:
- 主动向搜索引擎提交网址: 百度搜索资源平台
- 在其它网站设置外链
- 搜索引擎会和
DNS
服务商进行合作,可以快速收录新的网站
通用爬虫并不是万物皆可爬,它也需要遵守规则:Robots
协议,协议会指明通用爬虫可以爬取网页的权限。Robots.txt
并不是所有爬虫都遵守,一般只有大型搜索引擎爬虫才会遵守。
通用爬虫工作流程:
爬取网页 -> 存储数据 -> 内容处理 -> 提供检索/排名服务
搜索引擎排名:
-
PageRank
值:根据网站的流量(pv
),流量越高,排名约靠前 - 竞价排名
通用爬虫的缺点:
- 只能提供和文本相关的内容(HTML,Word,PDF)等,但是不能提供多媒体(音乐,视频,图片)和二进制文件。
- 提供的结果千篇一律,不能针对不同领域的人提供不同的搜索结果。
- 不能理解人类语义上的检索。
DNS
: 把域名解析成IP
聚焦爬虫
爬虫程序员写的针对某种内容爬虫。(针对通用爬虫的缺点)
面向主题爬虫,面向需求爬虫,会针对某种特定的内容去爬取信息,而且会保证内容信息和需求尽可能相关。
HTTP&HTTPS
HTTP协议(HyperText Transfer Protocol,超文本传输协议)
:是一种发布和接收HTML
页面的方法。
HTTPS(Hypertext Transfer Protocol over Secure Socket Layer)
简单讲是HTTP
的安全版,在HTTP
下加入SSL
层。
SSL(Secure Sockets Layer 安全套接层)
主要用于Web
的安全传输协议,在传输层对网络连接进行加密,保障在Internet
上数据传输的安全。
- HTTP的端口号为80
- HTTPS的端口号为443
HTTP工作原理
网络爬虫抓取过程可以理解为模拟浏览器操作的过程。
浏览器的主要功能是向服务器发出请求,在浏览器窗口中展示您选择的网络资源,HTTP
是一套计算机通过网络进行通信的规则。
常用的请求报头:
- Host (主机和端口号): 对应网址URL中的Web名称和端口号,用于指定被请求资源的Internet主机和端口号,通常属于URL的一部分。
Connection (链接类型): 表示客户端与服务连接类型
- Client 发起一个包含
Connection:keep-alive
的请求,HTTP/1.1使用keep-alive
为默认值。 - Server收到请求后:如果 Server 支持 keep-alive,回复一个包含
Connection:keep-alive
的响应,不关闭连接; 如果 Server 不支持keep-alive
,回复一个包含Connection:close
的响应,关闭连接。 - 如果client收到包含
Connection:keep-alive
的响应,向同一个连接发送下一个请求,直到一方主动关闭连接。 -
keep-alive
在很多情况下能够重用连接,减少资源消耗,缩短响应时间,比如当浏览器需要多个文件时(比如一个HTML文件和相关的图形文件),不需要每次都去请求建立连接。
- Client 发起一个包含
- Upgrade-Insecure-Requests (升级为HTTPS请求): 升级不安全的请求,意思是会在加载 http 资源时自动替换成 https 请求,让浏览器不再显示https页面中的http请求警报。(HTTPS 是以安全为目标的 HTTP 通道,所以在 HTTPS 承载的页面上不允许出现 HTTP 请求,一旦出现就是提示或报错。)
- User-Agent (浏览器名称): 是客户浏览器的名称
Accept (传输文件类型): 指浏览器或其他客户端可以接受的MIME(Multipurpose Internet Mail Extensions(多用途互联网邮件扩展))文件类型,服务器可以根据它判断并返回适当的文件格式。
-
Accept: */*
:表示什么都可以接收。 -
Accept:image/gif
:表明客户端希望接受GIF图像格式的资源; -
Accept:text/html
:表明客户端希望接受html文本。 -
Accept: text/html, application/xhtml+xml;q=0.9, image/*;q=0.8
:表示浏览器支持的 MIME 类型分别是 html文本、xhtml和xml文档、所有的图像格式资源。html中文件类型的accept属性有哪些
-
- Referer (页面跳转处): 表明产生请求的网页来自于哪个URL,用户是从该 Referer页面访问到当前请求的页面。这个属性可以用来跟踪Web请求来自哪个页面,是从什么网站来的等。有时候遇到下载某网站图片,需要对应的referer,否则无法下载图片,那是因为人家做了防盗链,原理就是根据referer去判断是否是本网站的地址,如果不是,则拒绝,如果是,就可以下载;
- Accept-Encoding(文件编解码格式): 指出浏览器可以接受的编码方式。编码方式不同于文件格式,它是为了压缩文件并加速文件传递速度。浏览器在接收到Web响应之后先解码,然后再检查文件格式,许多情形下这可以减少大量的下载时间。例如:
Accept-Encoding:gzip;q=1.0, identity; q=0.5, *;q=0
- Accept-Language(语言种类): 指出浏览器可以接受的语言种类,如en或en-us指英语,zh或者zh-cn指中文,当服务器能够提供一种以上的语言版本时要用到。
- Accept-Charset(字符编码): 指出浏览器可以接受的字符编码。例如:
Accept-Charset:iso-8859-1,gb2312,utf-8
- Cookie (Cookie): 浏览器用这个属性向服务器发送Cookie。Cookie是在浏览器中寄存的小型数据体,它可以记载和服务器相关的用户信息,也可以用来实现会话功能
- Content-Type (POST数据类型): POST请求里用来表示的内容类型。例如:
Content-Type = Text/XML; charset=gb2312:
常用的响应报头(了解):
- Cache-Control:must-revalidate, no-cache, private: 告诉客户端,服务端不希望客户端缓存资源,在下次请求资源时,必须要从新请求服务器,不能从缓存副本中获取资源。
- Connection:keep-alive: 客户端服务器的tcp连接也是一个长连接,客户端可以继续使用这个tcp连接发送http请求
- Content-Encoding:gzip: 告诉客户端,服务端发送的资源是采用gzip编码的,客户端看到这个信息后,应该采用gzip对资源进行解码。
- Content-Type:text/html;charset=UTF-8: 告诉客户端,资源文件的类型,还有字符编码,客户端通过utf-8对资源进行解码,然后对资源进行html解析。
- Date:Sun, 21 Sep 2016 06:18:21 GMT: 服务端发送资源时的服务器时间,GMT是格林尼治所在地的标准时间。http协议中发送的时间都是GMT的,这主要是解决在互联网上,不同时区在相互请求资源的时候,时间混乱问题。
- Expires:Sun, 1 Jan 2000 01:00:00 GMT: 这个响应头也是跟缓存有关的,告诉客户端在这个时间前,可以直接访问缓存副本,很显然这个值会存在问题,因为客户端和服务器的时间不一定会都是相同的,如果时间不同就会导致问题。所以这个响应头是没有Cache-Control:max-age=*这个响应头准确的,因为max-age=date中的date是个相对时间.
- Pragma:no-cache: 这个含义与Cache-Control等同。
- Server:Tengine/1.4.6: 这个是服务器和相对应的版本,只是告诉客户端服务器的信息。
- Transfer-Encoding:chunked: 这个响应头告诉客户端,服务器发送的资源的方式是分块发送的。
响应状态码:
-
100~199
:表示服务器成功接收部分请求,要求客户端继续提交其余请求才能完成整个处理过程。 -
200~299
:表示服务器成功接收请求并已完成整个处理过程。常用200(OK 请求成功)。 -
300~399
:为完成请求,客户需进一步细化请求。例如:请求的资源已经移动一个新地址、常用302(所请求的页面已经临时转移至新的url)、307和304(使用缓存资源)。 -
400~499
:客户端的请求有错误,常用404(服务器无法找到被请求的页面)、403(服务器拒绝访问,权限不够)。 -
500~599
:服务器端出现错误,常用500(请求未完成。服务器遇到不可预知的情况)。
Cookie 和 Session:
因为服务器和客户端的交互仅限于请求/响应过程,结束之后便断开,在下一次请求时,服务器会认为新的客户端。为了维护他们之间的链接,让服务器知道这是前一个用户发送的请求,必须在一个地方保存客户端的信息
Cookie:通过在客户端 记录的信息确定用户的身份。
Session:通过在服务器端 记录的信息确定用户的身份。
urllib
urllib.request
linux中的py源码文件位置:
python自带:vim /usr/lib/python2.7/urllib2.py
pip安装:vim /usr/local/lib/python3.6/site-packages/django/http/cookie.py
urllib2.urlopen
# -*- coding:utf-8 -*-
import urllib.request as urllib2
# 返回类文件对象
response = urllib2.urlopen('http://www.baidu.com/')
# urlopen不支持构造
# 服务器返回类文件对象支持python文件对象的操作方法
# read()方法就是读取文件里面的全部内容,返回字符串
html = response.read()
print(html)
Request
# -*- coding:utf-8 -*-
import urllib.request as urllib2
ua_headres = {
'User_Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Mobile Safari/537.36'
}
# urllib2.Request(url, data, headres)
# 通过urllib2.Request()方法构造一个请求对象
requset = urllib2.Request('http://www.baidu.com', headers=ua_headres)
# 返回类文件对象, urlopen不支持构造
response = urllib2.urlopen(requset)
# 服务器返回类文件对象支持python文件对象的操作方法
# read()方法就是读取文件里面的全部内容,返回字符串
html = response.read()
print(html)
User_Agent
,是发送请求必须带的请求头
Response响应
response
是服务器响应的类文件,除了支持文件操作的方法外,常用的方法也有:respnse.getcode()
, response.geturl()
, response.info()
#condig=utf-8
import urllib.request as urllib2
# print(dir(urllib2))
ua_headres = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.4620.400 QQBrowser/9.7.13014.400'
}
request = urllib2.Request('http://www.baidu.com/', headers=ua_headres)
response = urllib2.urlopen(request)
html = response.read()
# 返回HTTP的响应码,成功返回200
# 4 服务器页面出错, 5 服务器问题
print(response.getcode())
# 返回实际数据的url,防止重定向问题
print(response.geturl())
# 返回服务器响应报头信息
print(response.info())
# print(dir(response))
User-Agent历史
如果用一个合法的身份去请求别人网站,就是欢迎的,所以就应该给这个代码加上一个身份,就是所谓的User-Agent
头。
urllib2
默认的User-Agent
头为:Python-urllib/x.y
(x
和y
是Python
主版本和次版本号,例如 Python-urllib/2.7
Mosaic
世界上第一个浏览器:美国国家计算机应用中心Netscape
,网景:Netscape
(支持框架)Microsoft
微软:Internet Explorer
第一次浏览器大战:网景公司失败
Mozilla
基金组织:Firefox
火狐 内核(Gecko内核)(浏览器支持内核开始,User-Agent
开始逐渐使用)
User-Agent
决定用户的浏览器,为了获取更好的HTML页面效果
IE就给自己披着了个Mozilla
的外皮
内核:
- Mozilla: Firefox (Gecko)
- IE: Trident
- Opera: Presto
- Linux: KHTML (like Gecko)
- Apple: Webkit (like KTML)
- Google: Chrome (like webkit)
add_header() & get_header()
add_header()
: 添加/修改 一个HTTP
报头 get_header()
: 获取一个已有的HTTP
报头值,只能第一个字母大写,其它的必须小写
# -*- coding:utf-8 -*-
import urllib.request as urllib2
import random
url = 'http://www.baidu.com/'
# 可以是User-Agent列表,也可以是代理列表。 作用:反反爬虫
ua_list = [
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36',
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50'
]
# 在User-Agent列表里随机选择一个User-Agent
user_agent = random.choice(ua_list)
# 构造一个请求
request = urllib2.Request(url)
# add_header()方法,添加/修改 一个HTTP报头
request.add_header('User-Agent', user_agent)
# get_header() 获取一个已有的HTTP报头值,只能第一个字母大写,其它的必须小写
request.get_header('User-agent')
response = urllib2.urlopen(request)
html = response.read()
print(html)
urllib.urlencode
编码:urlencode
位置:urllib.parse.urlencode(values)
。 其中values
所需要编码的数据,参数只能为字典
解码:unquote
: urllib.parse.unquote(values)
#conding=utf-8
import urllib.parse
test = {
'test': '我的'
}
# 通过urllib.urlencode()方法,将字典键值对按URL编码转换,从而能被web服务器接受。
enCodeTest = urllib.parse.urlencode(test)
# 冒号解析为等号
print(enCodeTest) # test=%E6%88%91%E7%9A%84
# 通过urllib.unquote()方法,把 URL编码字符串,转换回原先字符串。
print(urllib.parse.unquote(enCodeTest)) # test=我的
爬取百度贴吧起始页到结束页的案例
#conding=utf-8
import urllib.request
import urllib.parse
def loadPage(url, filename):
'''
作用: 根据url发送请求,获取服务器响应文件
url: 需要爬取的url地址
filename: 处理的文件名
'''
print('正在下载' + filename)
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
}
request = urllib.request.Request(url, headers=headers)
return urllib.request.urlopen(request).read()
def writePage(html, filenmae):
'''
作用:将html内容写入到本地
html: 服务器响应文件内容
'''
print('正在保存' + filenmae)
# 文件写入
with open(filenmae, 'w') as f: # with 之后,不需要做文件关闭还有其它上下文处理的操作 等同于 open(), write(), close()
f.write(html.decode('utf-8'))
print('-' * 30)
print('thanks')
def tiebaSpider(url, beginPage, endPage):
'''
作用: 贴吧爬虫调度器,负责组合处理
url: 贴吧url的前部分
beginPage: 起始页
endPage: 结束页
'''
for page in range(beginPage, endPage+1):
pn = (page - 1) * 50
filename = '第' + str(page) + '页.html'
fullurl = url + '&pn=' + str(pn)
# print(fullurl)
html = loadPage(fullurl, filename)
writePage(html, filename)
# print(html)
if __name__ == '__main__':
kw = input('请输入需要爬取的贴吧名:')
beginPage = int(input('请输入起始页:'))
endPage = int(input('请输入结束页:'))
# https://tieba.baidu.com/f?ie=utf-8&kw=javascirpt&fr=search
url = 'https://tieba.baidu.com/f?'
key = urllib.parse.urlencode({'kw': kw})
fullurl = url + key
tiebaSpider(fullurl, beginPage, endPage)
POST请求的模拟
Get
和Post
请求的区别:
-
Get
请求:查询参数在QueryString
里保存 -
Post
请求:查询参数在FormData
中保存
Post
请求:
# -*- coding:utf-8 -*-
import urllib.request
import urllib.parse
url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
key = input('请输入查询翻译的文字:')
# 发送到服务器的表单数据,如果是中文需要转码
fromdata = {
'i': key,
'from': 'AUTO',
'to': 'AUTO',
'smartresult': 'dict',
'client': 'fanyideskweb',
'salt': '1528127663128',
'sign': 'c060b56b628f82259225f751c12da59a',
'doctype': 'json',
'version': '2.1',
'keyfrom': 'fanyi.web',
'action': 'FY_BY_REALTIME',
'typoResult': 'false'
}
data = urllib.parse.urlencode(fromdata).encode('utf-8')
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
}
request = urllib.request.Request(url, data=data, headers=headers)
html = urllib.request.urlopen(request).read().decode()
print(html)
获取AJAX加载的内容
AJAX
一般返回的是JSON
,直接对AJAX
地址进行post
或get
,就返回JSON
数据了。
“作为一名爬虫工程师,最需要关注的是数据的来源”
# -*- coding:utf-8 -*-
import urllib.request
import urllib.parse
url = 'https://movie.douban.com/j/search_subjects?'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
}
formdata = {
'type': 'movie',
'tag': '热门',
'sort': 'recommend',
'page_limit': 20,
'page_start': 40
}
data = urllib.parse.urlencode(formdata).encode('utf-8')
request = urllib.request.Request(url, data=data, headers=headers)
html = urllib.request.urlopen(request).read().decode()
print(html)
处理HTTPS请求 SSL证书验证
网站的SSL证书
是经过CA认证
的,则能够正常访问
单独处理SSL证书,让程序忽略SSL证书验证错误
# -*- coding:utf-8 -*-
import urllib.request
import ssl
# 表示忽略未经核实的SSL证书认证
context = ssl._create_unverified_context()
url = "https://www.12306.cn/mormhweb/"
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"
}
request = urllib.request.Request(url, headers = headers)
# 在urlopen()方法里 指明添加 context 参数
response = urllib.request.urlopen(request, context = context)
print(response.read())
CA
: 数字证书认证中心的简称,是指发放、管理、废除数字证书的受信任的第三方机构(类似与身份证)CA
的作用: 检查证书持有者身份的合法性,并签发证书,以防证书被伪造或篡改,以及对证书和密钥进行管理
一般正常的网站都会主动出示自己的数字证书,来确保客户端和网站服务器之间的通信数据是加密安全的.
Handler和Opener的使用
自定义Handler
# -*- coding:utf-8 -*-
import urllib.request
# 构建一个HTTPHandler处理器对象,支持处理HTTP的请求
# http_hander = urllib.request.HTTPHandler()
http_hander = urllib.request.HTTPHandler(debuglevel=1)
# 调用build_opener()方法构建一个自定义的opener对象,参数是构建的处理器对象
opener = urllib.request.build_opener(http_hander)
req = urllib.request.Request('http://www.baidu.com')
res = opener.open(req)
print(res.read().decode())
开放代理和私密代理
高匿:无法拿到真正的物理ip,只能获取代理服务器ip
透明:能看到代理服务器ip,也可以看到物理ip地址
快代理
西刺免费代理
使用代理IP,这是爬虫/反爬虫的第二大招,通常也是最好用的。
很多网站会检测某一段时间某个IP的访问次数(通过流量统计,系统日志等),如果访问次数多的不像正常人,它会禁止这个IP的访问。
可以设置一些代理服务器,每隔一段时间换一个代理,就算IP被禁止,依然可以换个IP继续爬取。
# -*- coding:utf-8 -*-
import urllib.request
# 代理开关,是否启用代理
proxyswitch = True
# 公开代理
proxy_ip = {
'http': '123.57.217.208:3128'
}
# 私密代理 授权的账号密码
# proxy_ip_auth = {
# 'http': 'user:passwd@ip:prot'
# }
# 构建一个handler对象,参数是一个字典类型,包括代理类型和代理服务器ip+prot
http_proxy_handler = urllib.request.ProxyHandler(proxy_ip)
# 构建一个没有代理对象的处理器对象
null_proxy_headler = urllib.request.ProxyHandler({})
if proxyswitch:
opener = urllib.request.build_opener(http_proxy_handler)
else:
opener = urllib.request.build_opener(null_proxy_headler)
# 构建一个全局的opener,之后的所有请求都可以用urlopen()方式发送,也附带Handler功能
urllib.request.install_opener(opener)
request = urllib.request.Request('http://www.baidu.com/')
response = urllib.request.urlopen(request)
# response = opener.open(request)
print(response.read().decode())
ProxyBasicAuthHandler
(代理授权验证):
#conding=utf-8
import urllib.request
user = ''
passwd = ''
proxyserver = ''
# 构建一个密码管理对象,用来保存需要处理的用户名和密码
passwdmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
# 添加账户信息,第一个参数realm是与远程服务器相关的域信息,一般都是写None,后面三个参数分别是 代理服务器、用户名、密码
passwdmgr.add_password(None, proxyserver, user, passwd)
# 构建一个代理基础用户名/密码验证的ProxyBasicAuthHandler处理器对象,参数是创建的密码管理对象
proxy_auth_handler = urllib.request.ProxyDigestAuthHandler(passwdmgr)
# 通过 build_opener()方法使用这些代理Handler对象,创建自定义opener对象,参数包括构建的 proxy_handler 和 proxyauth_handler
opener = urllib.request.build_opener(proxy_auth_handler)
request = urllib.request.Request('http://www.baidu.com/')
response = opener.open(request)
print(response.read().decode())
Cookie
Cookie 是指某些网站服务器为了辨别用户身份和进行Session跟踪,而储存在用户浏览器上的文本文件,Cookie可以保持登录信息到用户下次与服务器的会话。
HTTP是无状态的面向连接的协议, 为了保持连接状态, 引入了Cookie机制 Cookie是http消息头中的一种属性
Cookie名字(Name)
Cookie的值(Value)
Cookie的过期时间(Expires/Max-Age)
Cookie作用路径(Path)
Cookie所在域名(Domain),
使用Cookie进行安全连接(Secure)。
前两个参数是Cookie应用的必要条件,另外,还包括Cookie大小(Size,不同浏览器对Cookie个数及大小限制是有差异的)。
Cookie由变量名和值组成,根据 Netscape公司的规定,Cookie格式如下:
Set-Cookie: NAME=VALUE;Expires=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE
在Python
处理Cookie
,一般是通过cookielib
模块和urllib2
模块的HTTPCookieProcessor
处理器类一起使用。
cookielib
模块:主要作用是提供用于存储cookie
的对象
HTTPCookieProcessor
处理器:主要作用是处理这些cookie
对象,并构建handler
对象。
cookielib 库
该模块主要的对象有CookieJar
、FileCookieJar
、MozillaCookieJar
、LWPCookieJar
一般情况下,只用CookieJar()
,如果需要和本地文件交互,就需要使用MozillaCookjar()
和LWPCookieJar()
获取Cookie,并保存到CookieJar()对象中:
#!/usr/local/bin/python
import urllib.request
import http.cookiejar
cookiejar = http.cookiejar.CookieJar()
http_handler = urllib.request.HTTPCookieProcessor(cookiejar)
opener = urllib.request.build_opener(http_handler)
opener.open('http://www.baidu.com')
cook_str = ''
for item in cookiejar:
cook_str = cook_str + item.name + '=' + item.value + ';'
print(cook_str[:-1])
# BAIDUID=5DB0FC0C0DC9692BB8EE6EDC93A2EDEA:FG=1;BIDUPSID=5DB0FC0C0DC9692BB8EE6EDC93A2EDEA;H_PS_PSSID=1468_26259_21099_26350_26580;PSTM=1528615563;BDSVRTM=0;BD_HOME=0
访问网站获得cookie,并把获得的cookie保存在cookie文件中:
#!/usr/local/bin/python
import http.cookiejar
import urllib.request
filename = 'cookie.txt'
# 声明一个MozillaCookieJar(有save实现)对象实例来保存cookie,之后写入文件
cookiejar = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookiejar)
opener = urllib.request.build_opener(handler)
req = opener.open('http://www.baidu.com')
# 保存cookie到本地文件
cookiejar.save()
print(1)
非结构化数据和结构化数据
实际上爬虫一共就四个主要步骤:
- 明确目标 (要知道准备在哪个范围或者网站去搜索)
- 爬 (将所有的网站的内容全部爬下来)
- 取 (去掉对没用处的数据)
- 处理数据(按照想要的方式存储和使用)
re模块
pattern = re.compile(regExp)
pattern.match(): 从起始位置开始查找,返回第一个符合规则的,只匹配一次。
pattern.search(): 从任意位置开始查找,返回第一个符合规则的,只匹配一次。
pattern.findall(): 所有的全部匹配,返回列表
pattern.split(): 分割字符串,返回列表
pattern.sub(): 替换
rs.I 忽略大小写
re.S 全文匹配
match(str, begin, end)
:
>>> pattern = re.compile(r'([a-z]+) ([a-z]+)', re.I)
>>> m = pattern.match('hello world hello python')
>>> m.group()
'hello world'
>>> m.group(1)
'hello'
>>> m.group(2)
'world'
findall(str, begin, end)
:
>>> pattern = re.compile(r'\d+')
>>> pattern.findall('hello world 123 456 789')
['123', '456', '789']
>>>
split(str, count)
:
>>> pattern = re.compile(r'[\s\d\;]+')
>>> pattern.split('a b\a;m; a ')
['a', 'b\x07', 'm', 'a', '']
>>>
>>> pattern = re.compile('[\s\d\;]+')
>>> pattern.split(r'a b\a;m; a ')
['a', 'b\\a', 'm', 'a', '']
>>>
sub()
:
>>> pattern = re.compile(r'(\w+)(\w+)')
>>> strs = 'hello 123, world 456'
>>> pattern.sub('hello world', strs)
'hello world hello world, hello world hello world'
>>>
xpath
chrome
插件:XPath Helper
XPath (XML Path Language)
是一门在 XML
文档中查找信息的语言,可用来在XML
文档中对元素和属性进行遍历。
lxml
库:lxml
是 一个HTML/XML
的解析器,主要的功能是如何解析和提取HTML/XML
数据。
获取属性:@src
, @title
, @class
获取内容: /text()
模糊查询: contains(@id, '模糊字符串')
xpath
匹配规则:
//div[@class="pic imgcover"]/img/@src
xpath
模糊匹配:
//div[contains(@id, 'qiushi_tag')]
获取某个网站的图片:
#conding=utf-8
import urllib.request
import urllib.parse
from lxml import etree # 我乃河北,姓氏颜良
class getQdailyImg:
def __init__(self, url):
self.url = url
def loadPage(self):
print('正在下载...')
headres = {
'User-Agent': 'ie 的user-Agent'
}
req = urllib.request.Request(self.url, headers=headres)
html = urllib.request.urlopen(req).read().decode()
xmlDom = etree.HTML(html)
linkList = xmlDom.xpath('//div[@class="pic imgcover"]/img/@src')
print(linkList)
self.writePage(linkList)
def writePage(self, data):
for item in data:
with open('img.txt', 'w') as f:
f.write(item)
if __name__ == '__main__':
qdI = getQdailyImg('http://www.qdaily.com/')
qdI.loadPage()
# -*- coding:utf-8 -*-
import urllib.request
import json
from lxml import etree
url = 'http://www.qiushibaike.com/8hr/page/1'
headers = {
'user-agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB7.0)'
}
req = urllib.request.Request(url, headers=headers)
html = urllib.request.urlopen(req).read()
text = etree.HTML(html)
# 作为根目录节点
node_list = text.xpath('//div[contains(@id, "qiushi_tag")]')
items = {}
for node in node_list:
username = node.xpath('./div[@class="author clearfix"]//h2/text()')[0]
image = node.xpath('.//div[@class="thumb"]//@src')
content = node.xpath('.//div[@class="content"]/span')[0].text
zan = node.xpath('.//i')[0].text
comment = node.xpath('.//i')[1].text
items = {
'username': username,
'image': image,
'content': content,
'zan': zan,
'comment': comment
}
with open('qiushi.json', 'a') as f:
f.write(json.dumps(items, ensure_ascii=False) + '\n')
print('ok')
BeautifulSoup
Beautiful Soup
也是一个HTML/XML
的解析器,主要的功能也是如何解析和提取 HTML/XML
数据。
BeautifulSoup
用来解析HTML
比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持lxml
的 XML解析器。
pip install bs4
beautifulsoup4文档
- tag:
BeautifulSoup(html).div
- attrs:
BeautifulSoup(html).div.name
,BeautifulSoup(html).div.attres
- content:
BeautifulSoup(html).span.string
#conding=utf-8
from bs4 import BeautifulSoup
import requests
import time
def captchaMethod(captcha_data):
with open('captcha.jpg', 'wb') as f:
f.write(captcha_data)
return input('请输入验证码:')
def getLoginZhihu():
# 构建Session对象,保存cookie值
sess = requests.Session()
headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36 QQBrowser/4.3.4986.400'
}
html = sess.post('https://www.zhihu.com/#sigin', headers=headers).text
bs = BeautifulSoup(html, 'lxml')
_xsrf = bs.find('input', attrs={'name': '_xsrf'}).get('value')
captcha_url = 'https://www.zhihu.com/captcha.gif?r=%d&type=login' % (time.time() * 1000)
captcha = sess.get(captcha_url, headers=headers).content
# 获取验证码文字
text = captchaMethod(captcha)
data = {
'_xsrf': _xsrf,
'email': '123636374@qq.com',
'password': 'ALARMCHIME',
'captcha': text
}
# 登录 获取cookie
res = sess.post('https://www.zhihu.com/login/email', data=data, headers=headers).text
res = sess.get('https://www.zhihu.com/people/', headers)
if __name__ == '__main__':
getLoginZhihu()
JSON和JSONPATH
Json
和JsonPath
应用
-
json.loads()
: 把Json格式字符串解码转换成Python对象 -
json.dumps()
: 实现python类型转化为json字符串,返回一个str对象 把一个Python对象编码转换成Json字符串 -
json.dump()
: 将Python内置类型序列化为json对象后写入文件 -
json.load()
: 读取文件中json形式的字符串元素 转化成python类型
dictStr = {"city": "北京", "name": "大猫"}
print(json.dumps(dictStr, ensure_ascii=False))
# {"city": "北京", "name": "大刘"}
listStr = [{"city": "北京"}, {"name": "大刘"}]
json.dump(listStr, open("listStr.json","w"), ensure_ascii=False)
strDict = json.load(open("dictStr.json"))
print(strDict)
# {u'city': u'\u5317\u4eac', u'name': u'\u5927\u5218'}
# -*- coding:utf-8 -*-
import json
import urllib.request
import jsonpath
headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36 QQBrowser/4.3.4986.400'
}
url = 'https://www.lagou.com/lbs/getAllCitySearchLabels.json'
request = urllib.request.Request(url, headers=headers)
response = urllib.request.urlopen(request)
html = response.read().decode()
unicodeStr = json.loads(html)
content = jsonpath.jsonpath(unicodeStr, '$..name')
print(content)
array = json.dumps(content, ensure_ascii=False)
with open('lagoucontent.json', 'w') as f:
f.write(array)
多线程爬虫
一个进程可能包括多个线程,线程之间执行任务,必须通过加锁方式控制它们(阻塞)
父线程和子线程都关系,只要父线程执行完,不管子线程如何,都一并结束
- 计算机的核心是CPU,CPU承担了所有的计算任务
- 一个CPU核心一次只能执行一个任务
多个CPU核心同时可以执行多个任务 - 一个CPU一次只能执行一个进程,其它进程处于非运行
- 进程里包含的执行单元叫线程
一个进程 可以包含 多个线程 - 一个进程的内存空间是共享的,每个进程里的线程都可以使用这个共享空间
一个线程在使用这个共享空间的时候,其它线程必须等待它结束 - 通过“锁”实现,作用就是防止多个线程使用当前内存空间。
先使用的线程会加锁,锁上该空间,其它线程就在等待。
进程:表示程序的一次执行
线程:CPU运算的基本调度单位
GIL: Python里的执行通行证,而且只有唯一个。拿到通行证的线程才会执行
Python 的多线程适用于:大量密集的I/O处理 (单独都任务,一个进程,只能执行一个任务)
Python 的多进程适用于:大量的密集并行计算
#conding=utf-8
import json
import threading
from queue import Queue
import requests
from lxml import etree
CREAWL_EXIT = False
PARSE_EXIT = False
'''
采集线程
'''
class ThreadCrawl(threading.Thread):
def __init__(self, threadName, pageQueue, dataQueue):
# threading.Thread.__init__(self)
super(ThreadCrawl, self).__init__() # 多个父类,多重继承
self.threadName = threadName
self.pageQueue = pageQueue
self.dataQueue = dataQueue
self.headers = {
'user-agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB7.0)'
}
def run(self):
print('start' + self.threadName)
while not CREAWL_EXIT:
try:
page = self.pageQueue.get(False)
url = 'https://www.qiushibaike.com/8hr/page/' + str(page) + '/'
res = requests.get(url, headers=self.headers).text
self.dataQueue.put(res)
except:
pass
print('end' + self.threadName)
'''
解析线程
'''
class ThreadParse(threading.Thread):
def __init__(self, threadingName, dataQueue, filename):
super(ThreadParse, self).__init__()
self.threadingName = threadingName
self.dataQueue = dataQueue
self.filename = filename
def run(self):
print('start' + self.threadingName)
while not PARSE_EXIT:
try:
html = self.dataQueue.get(False)
self.parse(html)
except:
pass
print('end' + self.threadingName)
def parse(self, html):
text = etree.HTML(html)
node_list = text.xpath('//div[contains(@id, "qiushi_tag")]')
items = {}
for node in node_list:
username = node.xpath('./div[@class="author clearfix"]//h2/text()')[0]
image = node.xpath('.//div[@class="thumb"]//@src')
content = node.xpath('.//div[@class="content"]/span')[0].text
zan = node.xpath('.//i')[0].text
comment = node.xpath('.//i')[1].text
items = {
'username': username,
'image': image,
'content': content,
'zan': zan,
'comment': comment
}
self.filename.write(json.dumps(items, ensure_ascii=False) + '\n')
def main():
# 页码
pageQueue = Queue(10)
# 放入1~10的数字
for i in range(1, 10+1):
pageQueue.put(i)
# 采集结果(每页的HTML源码)
dataQueue = Queue()
filename = open('duanzi.json', 'a')
crawlList = ['采集线程1', '采集线程2', '采集线程3']
threadcrawl = []
for threadName in crawlList:
thread = ThreadCrawl(threadName, pageQueue, dataQueue)
thread.start()
threadcrawl.append(thread)
parseList = ['解析线程1', '解析线程2', '解析线程3']
threadparse = []
for threadName in parseList:
thread = ThreadParse(threadName, dataQueue, filename)
thread.start()
threadparse.append(thread)
# 等待pageQueue队列为空, 或者 数据队列为空,也就是等待之前执行的操作执行完毕
while not pageQueue.empty() or not dataQueue.empty():
pass
global CREAWL_EXIT
CREAWL_EXIT = True
print('queue队列为空')
global PARSE_EXIT
PARSE_EXIT = True
print('data队列为空')
for threadItem in crawlList:
threadItem.join('')
print('1')
if __name__ == '__main__':
main()
自动化测试unittest
模块使用和模拟用户点击抓取数据(拿去ajax分页数据)
# -*- coding:utf-8 -*-
import unittest
from selenium import webdriver
from bs4 import BeautifulSoup as bs
class Douyu(unittest.TestCase):
def setUp(self):
self.driver = webdriver.PhantomJS()
# unittest测试方法必须有`test`字样开头
def testDouyu(self):
self.driver.get('https://www.douyu.com/directory/all')
while True:
soup = bs(self.driver.page_source, 'lxml')
names = soup.find_all('h3', {'class': 'ellipsis'})
viewNums = soup.find_all('span', {'class': 'dy-num fr'})
for name, viewNum in zip(names, viewNums):
print('房间名' + name.get_text() + '; ' + '观众人数' + viewNum.get_text())
# 在页面源码中找到"下一页"未隐藏的标签,就退出循环
if self.driver.page_source.find('shark-pager-disable-next') != -1:
break
# 一直点击下一页
self.driver.find_element_by_class_name('shark-pager-next').click()
# 测试结束执行的方法
def tearDown(self):
self.driver.quit()
if __name__ == '__main__':
unittest.main()
执行javascript
语句:execute_script
#conding=utf-8
from selenium import webdriver
import time
driver = webdriver.PhantomJS('/Users/linxingzhang/Library/Python/3.6/lib/python/site-packages/selenium/webdriver/phantomjs')
driver.get("https://movie.douban.com/typerank?type_name=剧情&type=11&interval_id=100:90&action=")
time.sleep(3)
# 向下滚动10000像素
js = "document.body.scrollTop=10000"
# js="var q=document.documentElement.scrollTop=10000"
# 查看页面快照
driver.save_screenshot("douban.png")
# 执行JS语句
driver.execute_script(js)
time.sleep(10)
# 查看页面快照
driver.save_screenshot("newdouban.png")
driver.quit()
投票
import datetime
import sys
import threading
import time
from random import choice # choice() 方法返回一个列表,元组或字符串的随机项
import requests
from lxml import etree
from fake_useragent import UserAgent # 引入随机的UA
# 设置user-agent列表,每次请求时,随机挑选一个user-agent
ua_list = UserAgent()
def get_ip():
'''
获取代理ip
'''
url = 'http://www.xicidaili.com/nn'
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Host': 'www.xicidaili.com',
'Referer': 'http: // www.xicidaili.com/nn',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6726.400 QQBrowser/10.2.2265.400'
}
ret = requests.get(url, headers=headers)
xmlDom = etree.HTML(ret.text)
data = xmlDom.xpath('//table[@id="ip_list"]//tr')
z = []
for tr in data:
if tr.xpath('td'):
ip = tr.xpath('td')[1].text # 获取所有IP
port = tr.xpath('td')[2].text # 获取所有端口
z.append(ip + ':' + port)
return z
def get_url(url, code=0, ips=[]):
'''
投票
如果因为代理IP已失效造成投票失败,则会自动换一个代理IP后继续投票
'''
try:
ip = choice(ips)
print(ip, 'ip' * 5)
except:
return False
else:
proxies = {
'http': ip
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Host': 'best.zhaopin.com',
'Origin': 'https: // best.zhaopin.com',
'Referer': 'https//best.zhaopin.com/?sid=121128100&site=sou',
'User-Agent': ua_list.random
}
print(ua_list.random, 'ua_list' * 5)
try:
data = {'bestid': '3713', 'score': '5,5,5,5,5,5', 'source': 'best'}
# 跳过证书的验证 verify=False
result = requests.post(url, data=data, headers=headers, proxies=proxies)
print(result, 'result' * 5)
except requests.exceptions.ConnectionError:
print('ConnectionError')
if not ips:
print('ip 失效')
sys.exit()
# 删除不可用的代理IP
if ip in ips:
ips.remove(ip)
# 重新请求url
get_url(url, code=0, ips=[])
else:
date = datetime.datetime.now().strftime('%H:%M:%S')
# result.text() 投票成功显示1 失败显示0
print('第%s次 [%s] [%s]:投票%s (剩余可用代理IP数:%s)' %
(code, date, ip, result.text, len(ips)))
def main():
url = 'https://best.zhaopin.com/API/ScoreCompany.ashx' # 投票的请求
ips = []
for i in range(6000):
if i % 1000 == 0:
ips.extend(get_ip())
# print('-' * 100)
# print(ips)
t = threading.Thread(target=get_url, args=(url, i, ips))
t.start()
time.sleep(1)
if __name__ == '__main__':
main()
Tesseract
机器识别中的文字识别
pip install pytesseract
识别图片中的文字:
#conding=utf-8
import pytesseract
from PIL import Image
image = Image.open('./mayday.jpg')
text = pytesseract.image_to_string(image)
print(text)
asyncio & aiohttp
通过异步库aiohttp
,asyncio
爬取图片
# -*- coding:utf-8 -*-
import asyncio
import os
import time
import aiohttp
import requests
class Spider(object):
def __init__(self):
self.num = 1
if 'load-img' not in os.listdir('.'):
os.mkdir('load-img')
self.path = os.path.join(os.path.abspath('.'), 'load-img')
os.chdir(self.path) # 进入文件下载路径
def run(self):
start = time.time()
for x in range(1, 101): # 爬取100张图片,更改数值,爬取更多图片
links = self.__get_img_links(x)
tasks = [asyncio.ensure_future(self.__download_img(
(link['id'], link['links']['download'])
)) for link in links]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
# if self.num >= 10:
# break
end = time.time()
print('run %s s' % (end - start))
def __get_img_links(self, page):
url = 'https://unsplash.com/napi/photos'
data = {
'page': page,
'per_page': 12,
'order_by': 'latest'
}
response = requests.get(url, params=data)
if response.status_code == 200:
return response.json()
else:
print('request %s' % response.status_code)
async def __download_img(self, img):
content = await self.__get_content(img[1])
with open(img[0] + '.jpg', 'wb') as f:
f.write(content)
print('load %s page success' % self.num)
self.num += 1
async def __get_content(self, link):
async with aiohttp.ClientSession() as session:
response = await session.get(link)
content = await response.read()
return content
def main():
spider = Spider()
spider.run()
if __name__ == '__main__':
main()