Python_爬虫基础

爬虫概念

数据获取的方式:

  • 企业生产的用户数据:大型互联网公司有海量用户,所以他们积累数据有天然优势。有数据意识的中小型企业,也开始积累的数据。
  • 数据管理咨询公司
  • 政府/机构提供的公开数据
  • 第三方数据平台购买数据
  • 爬虫爬取数据

什么是爬虫

抓去网页数据的程序

网页三大特征:

  • 每个网页都有自己的URL
  • 网页都使用HTML标记语言来描述页面信息
  • 网页都使用HTTP/HTTPS协议来传输HTML数据

爬虫的设计思路

  1. 确定需要爬取的网页URL地址
  2. 通过HTTP/HTTPS协议来获取对应的HTML页面
  3. 提取HTML页面中的数据
    如果是需要的数据,就保存起来
    如果页面是其它URL,那就继续爬取

如何抓取HTML页面
HTTP协议请求的处理,urllib, urllib2, requests,处理后的请求可以模拟浏览器发送请求,获取服务器响应的文件

解析服务器响应的内容
re, xpath(常用), BeautifulSoup4(bs4), jsonpath, pyquery等使用某种描述性一样来给需要提取的数据定义一个匹配规则,符合这个规则的数据就会被匹配。

如何采集动态HTML,验证码的处理
Selenium(自动化测试工具) + PhantomJS(无界面浏览器)
验证码处理通过Tesseract: 机器图像识别系统(图片中的文本识别)

Scrapy框架
(Scrapy, Pyspider)

  • 高性能高定制型(异步网络框架twisted),所以数据下载速度快
  • 提供了数据存储,数据下载,提取规则等组件

分布式策略

  1. 是否有那么多的机器去做分布式?
  2. 获取的数据是否值得搭建分布式系统?

使用scrapy-redis来搭建,在Scrapy的基础上添加了一套 Redis数据库为核心的一套组件,让Scrapy框架支持分布式的功能。主要在Redis中做请求指纹去重请求分配数据临时存储

爬虫 – 反爬虫 – 反反爬虫

反爬虫: User-Agent, IP, 代理, 验证码, 动态数据加载, 加密数据
数据的价值,是否值得去费劲去做反爬虫,一般做到代理阶段或封IP
机器成本 + 人力成本 > 数据价值

爬虫和反爬虫之间的斗争,最后一定是爬虫获胜。
只要是真实用户可以浏览的网页数据,爬虫就一定能爬下来。(爬虫模拟浏览器获取数据)

爬虫集合awesome-spider

通用爬虫

搜索引擎使用的爬虫系统

目标:尽可能把互联网上所有的网页下载下来,放到本地服务器里形成备份,再对这些网页做相关处理(提取关键字,去掉广告),最后提供一个用户检索接口

抓取流程:

  • 首先选取一部分已有的URL,把这些URL放到待爬取队列。
  • 从队列里去取出这些URL,然后解析DNS得到主机IP,然后去这个IP对应的服务器下载HTML页面,保存到搜索引擎的本地服务器里,之后把这个已经爬过的URL放入到已经爬取队列中
  • 分析网页内容,找出网页中的其它URL内容,继续爬取。

搜索引擎如何获取一个新网站的URL:

  • 主动向搜索引擎提交网址: 百度搜索资源平台
  • 在其它网站设置外链
  • 搜索引擎会和DNS服务商进行合作,可以快速收录新的网站

通用爬虫并不是万物皆可爬,它也需要遵守规则:
Robots协议,协议会指明通用爬虫可以爬取网页的权限。
Robots.txt并不是所有爬虫都遵守,一般只有大型搜索引擎爬虫才会遵守。

通用爬虫工作流程:
爬取网页 -> 存储数据 -> 内容处理 -> 提供检索/排名服务

搜索引擎排名:

  • PageRank值:根据网站的流量(pv),流量越高,排名约靠前
  • 竞价排名

通用爬虫的缺点:

  1. 只能提供和文本相关的内容(HTML,Word,PDF)等,但是不能提供多媒体(音乐,视频,图片)和二进制文件。
  2. 提供的结果千篇一律,不能针对不同领域的人提供不同的搜索结果。
  3. 不能理解人类语义上的检索。

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 (链接类型): 表示客户端与服务连接类型

    1. Client 发起一个包含 Connection:keep-alive 的请求,HTTP/1.1使用 keep-alive 为默认值。
    2. Server收到请求后:如果 Server 支持 keep-alive,回复一个包含 Connection:keep-alive 的响应,不关闭连接; 如果 Server 不支持keep-alive,回复一个包含 Connection:close 的响应,关闭连接。
    3. 如果client收到包含 Connection:keep-alive 的响应,向同一个连接发送下一个请求,直到一方主动关闭连接。
    4. keep-alive在很多情况下能够重用连接,减少资源消耗,缩短响应时间,比如当浏览器需要多个文件时(比如一个HTML文件和相关的图形文件),不需要每次都去请求建立连接。
  • Upgrade-Insecure-Requests (升级为HTTPS请求): 升级不安全的请求,意思是会在加载 http 资源时自动替换成 https 请求,让浏览器不再显示https页面中的http请求警报。(HTTPS 是以安全为目标的 HTTP 通道,所以在 HTTPS 承载的页面上不允许出现 HTTP 请求,一旦出现就是提示或报错。)
  • User-Agent (浏览器名称): 是客户浏览器的名称
  • Accept (传输文件类型): 指浏览器或其他客户端可以接受的MIME(Multipurpose Internet Mail Extensions(多用途互联网邮件扩展))文件类型,服务器可以根据它判断并返回适当的文件格式。

    1. Accept: */*:表示什么都可以接收。
    2. Accept:image/gif:表明客户端希望接受GIF图像格式的资源;
    3. Accept:text/html:表明客户端希望接受html文本。
    4. 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.yxyPython主版本和次版本号,例如 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请求的模拟

GetPost请求的区别:

  • 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地址进行postget,就返回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 库

该模块主要的对象有CookieJarFileCookieJarMozillaCookieJarLWPCookieJar

一般情况下,只用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.nameBeautifulSoup(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

JsonJsonPath应用

  • 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)

多线程爬虫

一个进程可能包括多个线程,线程之间执行任务,必须通过加锁方式控制它们(阻塞)
父线程和子线程都关系,只要父线程执行完,不管子线程如何,都一并结束

  1. 计算机的核心是CPU,CPU承担了所有的计算任务
  2. 一个CPU核心一次只能执行一个任务
    多个CPU核心同时可以执行多个任务
  3. 一个CPU一次只能执行一个进程,其它进程处于非运行
  4. 进程里包含的执行单元叫线程
    一个进程 可以包含 多个线程
  5. 一个进程的内存空间是共享的,每个进程里的线程都可以使用这个共享空间
    一个线程在使用这个共享空间的时候,其它线程必须等待它结束
  6. 通过“锁”实现,作用就是防止多个线程使用当前内存空间。
    先使用的线程会加锁,锁上该空间,其它线程就在等待。

进程:表示程序的一次执行
线程: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()
    原文作者:alogy
    原文地址: https://segmentfault.com/a/1190000014981939
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞