python爬虫教程


前言

写这篇教程的时候我也是刚入坑,有不对或者是没有提及的部分还请各位见谅。我争取在升华之后来完善这篇教程

pycharm官网


一、前情提要

1.基础知识

  1. http:服务端和客户端进行数据交换的一协议
  2. https:安全的超文本传输协议(http+s)
    - 加密方式:1.对称密钥加密 2. 非对称密钥加密 3.证书密钥加密
  3. requests模块:python中原生的一款基于网络请求的模块,用于模拟浏览器发送请求
  4. UA伪装:门户网站的服务器会检测对应请求的载体身份标识,如果检测到的请求载体是浏览器没事,如果检测到不是,就有可能拒绝该次请求

2.html基础

HTML 指的是超文本标记语言: HyperText Markup Language

在这里插入图片描述
<!DOCTYPE html> 声明为 HTML5 文档
<html> 元素是 HTML 页面的根元素
<head> 元素包含了文档的元(meta)数据,如 定义网页编码格式为 utf-8。
<title> 元素描述了文档的标题
<body> 元素包含了可见的页面内容
<h1> 元素定义一个大标题
<p> 元素定义一个段落

HTML 标签是由尖括号包围的关键词,比如 <html>
HTML 标签通常是成对出现的,比如 <b> 和 </b>
标签对中的第一个标签是开始标签,第二个标签是结束标签
开始和结束标签也被称为开放标签和闭合标签
在这里插入图片描述

3.网页操作

  1. 审查元素:
    - 网址前面加上’view-source:’(防止反爬)
    - 鼠标放到网址上,按下F12
    - 直接F12
    - 网页右键,检查(常用)
  2. 判断元素是否是动态加载
    - 页面element数据,找到对应元素
    - 抓包工具,查看页面的response(ALL-当前页-Response),ctrl+f,查找数据
    - 如果找得到,可以利用element获取数据
    - 如果找不到,数据就是动态加载,去XHR,看请求,找参数,发送请求,获取response,提取对应元素

4.AJAX

一种可以实现页面局部刷新的技术

  1. 请求的发送:
    - 用户从 UI 发送请求,JavaScript 中调用 XMLHttpRequest 对象(可扩展超文本传输请求)。
    - HTTP 请求由 XMLHttpRequest 对象发送到服务器。
    - 服务器使用 JSP,PHP,Servlet,ASP.net 等与数据库交互。
    - 检索数据。
    - 服务器将 XML 数据或 JSON 数据发送到 XMLHttpRequest 回调函数。
    - HTML 和 CSS 数据显示在浏览器上。
  2. 工作流程:
    在这里插入图片描述

5.请求和响应

  1. 请求:
    请求头:
    在这里插入图片描述

    post请求和get请求:
    与POST相比,GET 更简单也更快,并且在大部分情况下都能用。
    然而,在以下情况中,请使用POST请求:
    - 无法使用缓存文件(更新服务器上的文件或数据库)
    - 向服务器发送大量数据(POST没有数据量限制)
    - 发送包含未知字符的用户输入时,POST比GET更稳定也更可靠

    一般来说请求的参数较少或者无关紧要用get(id号等)
    请求参数较多或比较重要用post(密码等)
    在这里插入图片描述
    这就是get请求携带的参数
    在这里插入图片描述

  2. 响应
    响应类型有很多种text、content、json、image等
    响应头
    在这里插入图片描述
    这里就可以看见响应的具体内容
    如果像这种类似于键值对的就是json
    在这里插入图片描述
    这种的就是javascript
    在这里插入图片描述
    这就是image
    在这里插入图片描述

6.cookie和session

  1. cookie:
    Cookie 是在HTTP协议下,服务器或脚本可以维护客户工作站上信息的一种方式。Cookie 是由 Web服务器保存 在用户浏览器(客户端)上的小文本文件(内容通常经过加密),它可以包含有关用户的信息。无论何时用户链接到 服务器,Web站点都可以访问Cookie 信息,可以看作是浏览器缓存.

  2. session:
    Session的定义很抽象,在不同的场合中session一词的含义也很不相同.它可以代表服务器与浏览器的一次会话过程,指从一个浏览器窗口打开到关闭的这个期间.也可以用于指一类用来在客户端与服务器之间保持状态的解决方案.

  3. 使用:
    cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器.

    具体内容可以去看我的这篇博客
    还是讲的比较详细的

7.线程和进程

并发:一个cpu同时处理多个程序,但是再一个时间点只会处理一个
并行:多个cpu同时处理多个程序
同步:执行io操作时,等待执行完成才得到返回结果
异步:不必等执行三成就能得到返回结果

多线程是在一个cpu上创建多个子任务,当某个子任务休息时其他任务接着执行
多进程是利用多核cpu的优势,同时执行多个任务
协程的实现实在一个线程内实现的(线程切换的消耗大,所以对于并发编程,可以优先使用协程)

8.代理ip

说实话,我到现在还不会弄,没找到可以用的代理ip…

9.超级鹰

使用:
超级鹰官网,注册账号,注册项目,充值,开发文档选python(居然有易语言????)
根据指示来就行了,记得导包


二、通用爬虫

1.案例

案例:药监总局
思路:
1.通过element获取每个企业信息的href元素,通过在抓包工具中查看,搜索不到对应数据(在response中找不到),所以这个地址是动态加载出来的,需要发送请求
2.查看并构造参数,发送ajax请求(post携带参数),接受response(json类型)
3.提取id,再次构造参数和url,发送ajax请求,得到想要数据

import requests

if __name__ == '__main__':
    url = 'http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsList'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36'
    }
    id_list = []
    for page in range(1, 6):
        data = {
            "on": "true",
            "page": page,
            "pageSize": "15",
            "productName": "",
            "conditionType": "1",
            "applyname": "",
            "applysn": ""
        }
        # response为一页的数据
        json = requests.post(url=url, data=data, headers=headers).json()
        id_list.clear()
        # 提取json里面的'list'里面的'ID'
        for dic in json['list']:
            id_list.append(dic['ID'])
        post_url = 'http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsById'
        for post_id in id_list:
            # url拼接
            data = {'id': post_id}
            detail = requests.post(url=post_url, headers=headers, data=data).json()
            print(detail)


三、聚焦爬虫

1.数据解析

提取html标签中我们想要获得的数据

2.方法

  1. 正则
  2. bs4
  3. xpath(推荐)

3.xpath

  1. 路径表达式:

在这里插入图片描述

  1. 谓语:

/div/tr[1] :选取属于 bookstore 子元素的第一个 book 元素。
/div/tr[last()] :选取属于 bookstore 子元素的最后一个 book 元素。
/div/tr[last()-1] :选取属于 bookstore 子元素的倒数第二个 book 元素。
//div[@id=‘log_in’] :选取所有div元素,且这些元素拥有值为 log_in 的 class 属性。
/div/tr[price>35.00] :选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。

  1. 选取若干路径:

//book/title | //book/price 选取 book 元素的所有 title 和 price 元素。

4.案例

古诗文网模拟登录
思路:
1.定位验证码,识别信息
2.在登录成功的页面获取参数信息,并且配置参数,发送请求
3.此时出现了问题:第二次发送后返回的是登录页面
4.原因:http协议特性:无状态。也就是说发送第二次请求时,服务器并不会知道是已经登录的状态
5.需要用cookie记录状态,用到session会话对象携带cookie

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import requests
# 下载lxml模块
from lxml import etree

if __name__ == '__main__':
    url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400'
    }
    page_text = requests.get(url=url, headers=headers).text
    tree = etree.HTML(page_text)

    # session会话对象 携带请求产生的cookie
    session = requests.Session()

    # 验证码识别(这里先蠢一点,不用超级鹰)
    img_src = 'https://so.gushiwen.cn' + tree.xpath('//*[@id="imgCode"]/@src')[0]
    img_data = session.get(url=img_src, headers=headers).content
    with open('E:\\python-file/a.jpg', 'wb') as fr:
        fr.write(img_data)
    code_text = input('人眼识别验证码')

    # 登录
    login_url = 'https://so.gushiwen.cn/user/login.aspx'
    data = {
        '__VIEWSTATE': 'J+WuRAV4B/72GItcjC9ob6UNO1i0JoTIczXqRXVoXww8wUlRo3Xi8kLANgsQNCsS2Y8f2D1yNv6UNE98aAu0bJcKBtZUDKQ/4s+0Q//rUORWz/1Xt2sZL7UBi2U=',
        '__VIEWSTATEGENERATOR': 'C93BE1AE',
        "from": "http://so.gushiwen.cn/user/collect.aspx",
        "email": "*********",
        "pwd": "*********",
        "code": code_text,
        "denglu": '登录'
    }
    login_text = session.post(url=login_url, headers=headers, data=data).text
    with open('E:\\python-file/gushiwen.html', 'w', encoding='utf-8') as fr:
        fr.write(login_text)


四、增量爬虫

还没学,持续更新


五、异步爬虫

1.方式

  1. 多进程,多线程(不建议)
  2. 线程池,进程池
  3. 协程

2.线程池

先举个栗子,如果使用线程池

from multiprocessing.dummy import Pool

def fn(str):
    print('beg' + str)
    time.sleep(2)
    print('end' + str)

if __name__ == '__main__':
    beg = time.time();
    li = ['a', 'b', 'c', 'd']
    pool = Pool(len(li))
    pool.map(fn, li)
    end = time.time()
    print(end - beg)

梨视频:
思路:
1.查看element里面的链接是不是在response里面,这里不在,也就是发送ajax请求,获取response里的信息
2.拼接得到一个url,查看并不是视频的链接(反爬机制)
3.查看视频正确链接,与得到的url对比,修正url(反反爬,因为正确的链接也是通过代码合成的)
4.将上述得到的url储存进list
5.开线程池进行下载

#!/usr/bin/python
# -*- coding: UTF-8 -*-
import random
import time
import requests
from lxml import etree
import re
from multiprocessing.dummy import Pool

def get_url(item):
    detail_header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400',
        # 这个referer就是表明是从哪里来的
        'Referer': item['detail_url']
    }
    # 获取视频的url(发送ajax请求,根据返回json(在抓包工具里查看response)获取url)
    # 这里我觉得可以用param带id和mrd参数发送get请求
    video_url = 'https://www.pearvideo.com/videoStatus.jsp?contId={}&mrd={}'.format(item['id'], random.random())
    video_url = requests.get(url=video_url, headers=detail_header).json()["videoInfo"]["videos"]["srcUrl"]
    # url修正 (在ajax请求的返回里不是真正的url)
    # re.sub(替换)(其中三个必选参数:pattern(要被替换的), repl(被替换成的), string(被处理的))
    video_url = re.sub(r'16\d+-', r'cont-{}-'.format(item['id']), video_url)
    # 保存视频url
    item['video_url'] = video_url

def get_download(item):
	# 异步下载
    print(item['video_path'] + ' start')
    video_text = requests.get(url=item['video_url'], headers=headers).content
    with open(item['video_path'], 'wb') as fr:
        fr.write(video_text)
        print(item['video_path'] + ' over')

if __name__ == '__main__':
    # 进程池开4个大小
    pool = Pool(4)
    # 保存视频的id,detail_url,video_path,video_url
    items = []
    url = 'https://www.pearvideo.com/category_59'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400'
    }
    page_text = requests.get(url=url, headers=headers).text
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//*[@id="categoryList"]/li')
    # 处理视频数据
    for li in li_list:
        # 获取视频保存路径
        video_path = 'E:\\python-file/' + ''.join(li.xpath('./div/a/div[2]/text()')[0].split(' ')) + '.mp4'
        # 获取详情页url
        detail_url = 'https://www.pearvideo.com/' + li.xpath('./div/a/@href')[0]
        # 保存id,用于获取视频url
        dic = {
            'id': detail_url.split('_')[-1],
            'detail_url': 'https://www.pearvideo.com/' + li.xpath('./div/a/@href')[0],
            'video_path': video_path,
            'video_url': ''
        }
        items.append(dic)
	# 开启异步
    pool.map(get_url, items)
    pool.map(get_download, items)
    

3.协程

  1. 实现方法
    - greenlet
    - yield关键字
    - asyncio装饰器
    - async,await关键字(推荐)(就是3的关键字改进,代码简洁)
  2. task和future(我也比较迷):
    - Future,是对协程的封装,表明一个异步操做的最终结果,其值会在未来被计算出来。当一个Future对象被await的时候,表示当前的协程会持续等待,直到 Future对象所指向的异步操做执行完毕。
    - Task(Task是Future子类)是跟事件循环交互的一种主要方式。
    - 两种建立Task实例的方式:asyncio.ensure_future 和asyncio.create_task
    - 对于绝大多数场景要并发执行的是协程,直接用asyncio.create_task就够。(如果当前线程中没有正在运行 的事件循环,asyncio.create_task将会引起RuntimeError)
    - Future有四种状态:Pending, Running, Done, Cancelled
    建立Task实例后,Task实例在加入事件循环以前是pending状态。事件循环调用执行的时候固然就是running,调用完毕天然就是done,若是须要中止事件循环,就须要先把task取消。
    - coroutine封装为Task后不会立马启动,当某个代码await这个task的时候才会被执行
  3. 使用:
import asyncio
import aiohttp

async def get_page(url):
    print(1)
    # async with aiohttp.ClientSession() as session:
    #     # get()/post()
    #     async with await session.get(url=url) as response:
    #         # text():字符串形式
    #         # read():二进制形式
    #         # json():json对象
    #         # 注意:在获取响应数据之前一定要await进行手动挂起
    #         page_text = await response.text()
    await asyncio.sleep(2)
    print(2)

async def fn():
    urls = ['1', '2', '1']
    # 在事件循环中添加任务列表
    tasks = [asyncio.ensure_future(get_page(url)) for url in urls]
    # await(协程函数内部使用) + 可等待对象(协程对象,future,task)
    # await就是等待可等待对象执行完毕再往下走(这段时间可以切换到其他任务)
    # 如果是任务列表就要await asyncio.wait(tasks)
    # 如果是任务可以直接await task
    await asyncio.wait(tasks)

if __name__ == '__main__':
    # 运行程序
    asyncio.run(fn())

六、selenium

1.什么是selenium

selenium:基于浏览器自动化的一个模块

2.基本使用

  • 发起请求:get(url)
  • 标签定位:find
  • 标签交互:send_keys
  • 执行js程序:excute_script
  • 前进后退:back,fowards
  • 关闭:quit

3.案例

12306:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
import time
from chaojiying_Python.chaojiying import Chaojiying_Client
import re
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver import ActionChains
from selenium.webdriver import ChromeOptions


while True:
    bro = webdriver.Chrome(executable_path='./chromedriver')
    bro.get('https://kyfw.12306.cn/otn/resources/login.html')
    time.sleep(1)

    # # 无头浏览器
    # options = ChromeOptions()
    # options.add_argument('--headless')
    # options.add_argument('--disable-gpu')
    # bro = webdriver.Chrome(executable_path='./chromedriver', options=options)
    
    # 规避风险
    bro.execute_script('Object.defineProperty(navigator,"webdriver",{get:()=>undefined,});')

    bro.find_element_by_link_text('账号登录').click()
    time.sleep(1)

    # 局部截图
    img = bro.find_element_by_class_name('imgCode')
    img.screenshot('img.png')

    # 使用超级鹰
    chaojiying = Chaojiying_Client('1111', '0000', 'First_test')
    im = open('img.png', 'rb').read()
    all_code = chaojiying.PostPic(im, 9004)['pic_str']

    # 点击验证码(正则提取数字,利用索引提取坐标)
    code_list = re.findall("[0-9]+", all_code)
    for i in range(len(code_list)):
        if i % 2 == 0:
            ActionChains(bro).move_to_element_with_offset(img, float(code_list[i]),
                                                          float(code_list[i + 1])).click().perform()
            time.sleep(0.5)
        else:
            pass
    # 填写用户密码
    bro.find_element_by_id('J-userName').send_keys('1111')
    bro.find_element_by_id('J-password').send_keys('0000')
    time.sleep(1)
    bro.find_element_by_id('J-login').click()
    time.sleep(1)

    # 判断滑块是否出来
    span = bro.find_element_by_id('nc_1_n1z')
    if span.is_displayed():
        break
    else:
        pass

# 拖动滑块
action = ActionChains(bro)
action.click_and_hold(span)
action.move_by_offset(300, 0).perform()
action.release()

# # 判断是否登录成功
# try:
#     # 根据登录成功页面个人信息栏的标签
#     r = bro.find_element_by_class_name('site-nav-login-info-nick').text
#     print('登录成功')
#     break
# except NoSuchElementException:
#     print('登录失败,再次尝试')
#     time.sleep(1)

# 关闭页面
time.sleep(2)
bro.quit()



七、奇奇怪怪的知识点

1.正则快速加引号

ctrl + r :调取正则页面,上面用()括住,下面用"$1",全部替换即可
在这里插入图片描述

2.提取文本乱码

# 方法1:
t = requests.get(url=url, headers=headers)
t.encoding = 'utf-8'
page_text = text.text

# 方法2
page_text = requests.get(url=url, headers=headers).content

# 方法3
name = li.xpath('./a/img/@alt')[0] + '.jpg'
name = name.encode('iso-8859-1').decode('gbk')

3.pip install ***

别pip install 了,直接pycharm-setting
在这里插入图片描述

4.找不到标签(iframe)

iframe,在网页里嵌套子页面,如果想找的标签在iframe里面,需要切换浏览器标签定位的作用域
bro.switch_to.frame(‘标签名称’)
div = bro.find_element_by_id(‘标签名称’)

总结

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值