Python爬虫

概念

爬虫避免进局子的风险

  1. 时常的优化自己的程序,避免干扰被访问网站的正常运行
  2. 在使用,传播获取到的数据时,审查抓取到的内容,如果涉及到用户隐私,商业机密等敏感内容需要及时停止或传播

爬虫的分类:

  1. 通用爬虫:抓取系统重要组成部分,抓取的是一整张页面数据
  2. 聚焦爬虫:是建立在通用爬虫基础上的,爬取的是需求页面中特定的局部内容
  3. 增量式爬虫:检测网站中的数据更新情况,只会爬取网站中最新更新出来的数据;

爬虫的矛盾
反爬机制

  • 门户网站,可以通过相应的策略或者技术手段,防止爬虫程序进行网页数据的爬取
    反反爬策略
  • 爬虫程序可以通过制定相关的策略或技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站的数据;
    robots.txt协议:
  • 君子协议.规定了网站中哪些数据可以被爬虫程序抓取哪些不可以;

http协议

  • 概念: 就是服务器和客户端的进行数据交互的一种形式,超文本传输协议

常用的请求头:

  • User-Agent:请求载体的身份标识符
  • Connection:请求完毕后,是断开连接还是保持连接

常见的响应头

  • Content-Type:服务器响应回复客户端的数据类型

https协议

  • 安全的超文本传输协议

加密方式

  • 对称秘钥加密
  • 非对称秘钥加密
  • 证书加密

requests模块

  • urllib 模块
  • requests 模块

requests模块:python中原生的一款基于网络请求的模块,功能非常强大,简单便捷,效率极高.
作用:模拟浏览器发送请求
如何使用:

  • 指定url
  • 发起请求
  • 获取响应数据
  • 持久化存储

环境安装: pip install requests
实战编码:

import requests

heads = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"}


def get_url():
    kw = input('请输入检索关键字')
    return "https://cn.bing.com/search?q=" + kw


def get_data(url):
    res = requests.get(url, headers=heads)
    save_file("检索结果数据", res.text)


def save_file(name, data):
    with open(name + ".html", "w", encoding="utf-8") as wf:
        wf.write(data)


if __name__ == "__main__":
    url = get_url()
    get_data(url)

数据解析分类

  1. 正则
  2. bs4
  3. xpath (重点)

正则解析

ex=‘<div class="thumb">.*?<img src="(.*?)" alt.*?</div>’

数据解析概况:
解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储

  1. 进行指定标签定位
  2. 标签或者标签对应的属性中存储的数据进行提取(解析)

bs4进行数据解析

  1. 数据解析的原理
  2. 标签定位
  3. 提取标签、标签属性中存储的数据值
  4. bs4数据解析原理
    1. 通过实例化一个BeautifulSoup对象,并且将页面数据加载到该对象中
    2. 通过调用BeautifulSoup对象中的属性或方法进行标签定位和数据提取

环境安装

  1. pip install bs4
  2. pip install lxml

如何实例化BeautifulSoup对象:
from bs4 import BeautifulSoup
对象的实例化

  1. 将本地的html文档中的数据加载到该对象中
    fp=open('./test.html','r',encoding='utf-8')
    soup=BeautifulSoup(fp,'lxml')
  2. 将互联网上获取的页面源码加载到该对象中
    page_text = response.text
    soup=BeautifulSoup(page_text,'lxml')

提供的用于数据解析的方法和属性
soup.tagName:返回的是文档中第一次出现的tagName对应的标签,如soup.title
soup.find():find('tagname')等同于soup.div-属性定位soup.find('div',class_/id/attr='song)
soup.find_all('tagName',)返回符合要求的所有标签(列表)
select:
select('某种选择器(id,class,标签...选择器'))返回的是一个列表.
层级选择器:
--soup.select('.tang > ul > li > a')>标识是一个层级
--soup.select('.tang > ul a')>空格标识多个层级
获取标签之间的数据
soup.a.text/string.get_text()text/get_text()可以过去摸一个标签中所有的文本内容
string 值可以获取该标签下的文本内容
获取标签中的属性值
soup.a['href']

Xpath解析

xpath解析:最常用最便捷的高效的一种解析方式,通用性.
xpath 解析原理:

  1. 实例化一个etree的对象,且需要将其解析源码数据加载到该对象中
  2. 调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获.

环境的安装:

  1. pip install lxml
    使用:from lxml import etree
    如何实例化一个etree对象,且需要将解析的页面源码数据加载到该对象中.
  2. 将本地的html文档的数据源码加载到etree对象中:etree.parse(filePath, etree.HTMLParser())
  3. 可以将互联网上获取的源码数据加载到etree对象中:etree.HTML(str)

使用:result = html.xpath('//li')
print(result)
xpath表达式:

  1. /表示的是从根节点开始,表示的是一个层级
  2. //表示的是多个层级,可以表示从任意位置开始
  3. 属性定位://div[@class="song"] tag[@attrName="attrValue"]
  4. 索引定位://div[@class="song"]/p[3] 索引是从1开始的
  5. 取文本:
    1. /text()获取标签中直系的文本内容如://p[1]/span/a/text()
    2. //text()获取标签中非直系的文本内容(该标签所有文本内容)
  6. 取属性:
    1. /@attrName //img[@id="imgCode"]/@src'

验证码识别

验证码和爬虫之间的爱恨情仇?
反爬虫机制:验证码。识别验证码图片中的数据,用于模拟登录操作
识别验证码的操作:

  1. 人工肉眼识别
  2. 第三方 自动识别验证码识别技术开发文档-云码 (jfbym.com)
    ddddocr的库使用

云打码使用

  1. 进入打码平台注册账号登陆后使用接口调用即可
import requests
import base64

def get_ocr_img(filePath):
    url='https://www.jfbym.com/api/YmServer/verifyapi'
    base=encode_base64(filePath)
    data={
        "image":base,
        "token":"lvfJhEHuS+2Kk5HWWFGz2pYZOOiaDvLeIauN35puIc=",
        "type":"10101"
    }
    headers={
        "Content-Type":"application/x-www-form-urlencoded"
    }
    res=requests.post(url,data=data,headers=headers)
    print(res.text)

def encode_base64(file):
    with open(file, 'rb') as f:
        img_data = f.read()
        base64_data = base64.b64encode(img_data)
        print(type(base64_data))
        # print(base64_data)
        # 如果想要在浏览器上访问base64格式图片,需要在前面加上:data:image/jpeg;base64,
        base64_str = str(base64_data, 'utf-8')
        print(base64_str)
        return base64_data


def decode_base64(base64_data):
    with open('./images/base64.jpg', 'wb') as file:
        img = base64.b64decode(base64_data)
        file.write(img)


if __name__ == '__main__':
        get_ocr_img('./yzm.jpg')

http/https协议特征:无状态。
没有请求到对应页面数据的原因

  • 发起的第二次基于个人主页面请求的时候。服务器端并不知道该此请求是基于登录状态下的请求。

cookie:用来让服务器端记录客户端的相应状态:

  • 手动处理:通过抓包工具获取cookie值。将该值封装到headers中(不建议)
  • 自动处理:
    1. 创建一个session对象:session=requests.Session()
    2. 使用session对象进行模拟登录post请求的发送(cookie就会被存储在session中)
    3. session对象对个人主页对应的get请求发送(携带了cookie)

代理:破解封IP这种反爬机制。

什么是代理?-代理服务器
代理的作用:突破自身IP访问的限制,隐藏自身的真实IP
代理相关网站:快代理,西祠代理,www.goubanjia.com
代理IP的类型

  • http:应用到http协议对应的url中
  • https:应用到https协议对应的url中

使用方法:res=requests.get(url,headers=headers,proxies={'https':'103.103.3.6:8080'}).text
代理ip的匿名度

  • 透明:服务器知道该次请求使用了代理,也知道对应真是的IP地址
  • 匿名:知道了使用代理,不是到真实IP
  • 高匿:不知道使用了代理,不知道真是IP
import requests
url ='https://ip.hao86.com/'
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37'}
res=requests.get(url,headers=headers,proxies={'https':'10.103.3.6:8080'}).text
with open('./dd.html','w',encoding='utf-8') as wf:
    wf.write(res)

高性能异步爬虫

目的:在爬虫中使用异步实现高性能的数据爬取操作
异步爬虫的方式:

  • 多线程,多进程:(不建议)
    好处:可以为相关阻塞的操作单独开启线程或进程,阻塞操作就可以异步执行.
    弊端:无法限制的开启多线程或多进程.
  • 线程池,进程池(适当使用)
    好处:我们可以降低系统对进程或线程创建和销毁的一个频率,从而很好的降低系统的开销
    弊端:池中线程或进程的数量是有上限.
  • 单线程+异步协程(推荐):
    event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行
    coroutine 协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用.我们可以使用async关键字来顶一个方法,这个方法在调用时不会被执行,而是返回一个协程对象.
    task 任务,他是对协程对象的进一步封装,包含了任务的各个状态
    future:代表将来执行或者还没有执行的任务,实际上和task没有本质区别
    async定义一个协程
    await 用来挂起阻塞方法的执行.

不使用线程池方式

import time
def get_page(str):
    print('start-',str)
    time.sleep(2)
    print('end-',str)

name_list=['xiaozi','aaa','bbb','ccc']

start_time=time.time()
for i  in range(len(name_list)):
    get_page(name_list[i])

end_time=time.time()
print('%d sdde'%(end_time-start_time)) # 8 sdde

使用线程池方式

import time
from multiprocessing.dummy import Pool
def get_page(str):
    print('start-',str)
    time.sleep(2)
    print('end-',str)

name_list=['xiaozi','aaa','bbb','ccc']

start_time=time.time()
# for i  in range(len(name_list)):
#     get_page(name_list[i])
# 实例化线程池对象
pool=Pool(4)# 开启4个线程池
# 将列表中每一个列表元素传递给get_page进行处理,返回的结果就是get_page的return
pool.map(get_page,name_list)
end_time=time.time()
print('%d sdde'%(end_time-start_time)) # 2 sdde

使用单线程+异步协程

import asyncio
async def request_url(url):
    print('正在请求的url时',url)
    print('请求成功',url)
    return url

c=request_url('www.baidu.com')
# 创建一个事件循环对象
# loop=asyncio.get_event_loop()
# # 将协程对象注册到loop中然后启动loop
# loop.run_until_complete(c)
# asyncio.run(c) # 上述的封装方法

# task的使用
# loop=asyncio.get_event_loop()
# task=loop.create_task(c,name='task1')
# print(task)
# loop.run_until_complete(task)
# # future
# loop=asyncio.get_event_loop()
# task=asyncio.ensure_future(c)
# print(task)
# loop.run_until_complete(task)
# 绑定回调
def callback_func(task):
    print(task.result())
loop=asyncio.get_event_loop()
task=asyncio.ensure_future(c)
# 将回调函数绑定到任务对象中,task执行完成后就会调用该函数
task.add_done_callback(callback_func)
loop.run_until_complete(task)

import asyncio
import time
async def request_url(url):
    print('正在下载',url)
    await asyncio.sleep(2)
    print('下载完成',url)

start=time.time()
urls=[
    'baidu',
'google',
'sougou',
'bing'
]
# stasks=[]
# for ul in urls:
#     c=request_url(ul)
#     task=asyncio.ensure_future(c)
#     stasks.append(task)

tasks=[request_url(url) for url in urls]

loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
ends=time.time()
print(ends-start)

案例

import asyncio
import time
import requests
import aiohttp
urls=[
    'http://127.0.0.1:5000/b1',
'http://127.0.0.1:5000/b2',
'http://127.0.0.1:5000/b3',
]
async def get_page(url):
    print('正在下载',url)
    # requests.get是基于同步,必须使用基于异步的网络请求模块进行指定url的请求发送,或者给他弄成线程再转换为协程
    # aiohttp基于异步网络请求的模块
    async with aiohttp.ClientSession() as session:
        # get(),post()
        # headers,params/data,proxy='http://ip:port'
        async  with session.get(url) as res:
           pageText= await res.text()
    print('下载完毕',pageText)

tasks=[get_page(url) for url in urls]

start=time.time()
asyncio.run(asyncio.wait(tasks))
ends=time.time()
print('总耗时',ends-start) # 总耗时 2.0147018432617188

错误演示//request没有协程

import asyncio
import time
import requests

urls=[
    'http://127.0.0.1:5000/b1',
'http://127.0.0.1:5000/b2',
'http://127.0.0.1:5000/b3',
]
async def get_page(url):
    print('正在下载',url)
    res=requests.get(url)
    print('下载完毕',res.text)

tasks=[get_page(url) for url in urls]

start=time.time()
asyncio.run(asyncio.wait(tasks))
ends=time.time()
print('总耗时',ends-start)

后端代码 类似下方方式

from flask import Flask
import time
app=Flask(__name__)
@app.route('/b1')
def index_b1():
    time.sleep(2)
    return 'Hello b1'

@app.route('/b2')
def index_b2():
    time.sleep(2)
    return 'Hello b2'

@app.route('/b3')
def index_b3():
    time.sleep(2)
    return 'Hello b3'

if __name__=='__main__':
    app.run(threaded=True)



扩展Python自带的网络请求库

示例
发送get请求

import urllib.request

url='https://funletu.com/dong-tai/page/2'
req=urllib.request.Request(url)
with urllib.request.urlopen(req) as response:
    data=response.read()
    ss=data.decode()
    print(ss)

发送post请求

import urllib.request
import urllib.parse
url='https://funletu.com/dong-tai/page/2'
params_dict={'id':123,'query':'all'}
params_str=urllib.parse.urlencode(params_dict)
params_bytes=params_str.encode()
req=urllib.request.Request(url,data=params_bytes)
with urllib.request.urlopen(req) as response:
    data=response.read()
    ss=data.decode()
    print(ss)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容