本文仅供学习使用

Python高级——Ch23爬虫Spider

  • 23. 爬虫Spider
    • 23.1 HTTP基本原理
      • 23.1.1 URI与URL
      • 23.1.2 超文本
      • 23.1.3 HTTP 和 HTTPS
      • 23.1.4 HTTP请求过程
      • 23.1.5 请求
      • 23.1.6 响应
      • 23.1.7 网页的组成
      • 23.1.8 节点数及节点之间的关系
      • 23.1.9 选择器
      • 23.1.10 流程回顾
      • 23.1.11 能抓怎样的数据
      • 23.1.12 JavaScript渲染页面
    • 23.2 会话和Cookies
      • 23.2.1 静态网页和动态网页
      • 23.2.2 无状态HTTP
      • 23.2.3 会话维持
      • 23.2.4 代理的基本原理
    • 23.3 requests
      • 23.3.1 requests的用法
      • 23.3.2 带参数的get请求
      • 23.3.3 JSON解析
      • 23.3.4 简单保存一个二进制文件
      • 23.3.5 response.text和response.content的区别
      • 23.3.6 为请求添加头信息
      • 23.3.7 基本代理
      • 23.3.8 带参数的get请求
      • 23.2.9 cookie和session
      • 23.3.9 证书验证设置
      • 23.3.10 异常处理
    • 23.4 数据提取
      • 23.4.1 数据分类
      • 23.4.2 JSON数据提取
      • 23.4.3 正则表达式提取
      • 23.4.4 动态网站数据抓取
    • 23.5 Beautiful Soup
      • 23.5.1 BS4引入
      • 23.5.2 BS4选择元素
      • 23.5.3 BS4提取信息
      • 23.5.4 find_all方法选择器
      • 23.5.5 CSS选择器
    • 23.6 IXML
      • 23.6.1 节点与属性
      • 23.6.2 Xpath
      • 23.6.3 数据爬取
    • 23.7 小结
    • 23.8 MySQL
      • 23.8.1 MySQL的启动与关闭
      • 23.8.2 MySQL数据库的基础操作
      • 23.8.3 Python操作MySQL
      • 23.8.4 博客园爬取
    • 23.8 Scrapy
      • 23.8.1 异步与非阻塞的区别
      • 23.8.2 爬虫流程
      • 23.8.3 Scrapy 项目文件构成
      • 23.8.4 小结

23. 爬虫Spider

为什么要学习爬虫&爬虫的定义

· 网络爬虫又称网络蜘蛛、网络机器人等,可以自动化浏览网络中的信息,当然浏览信息的时候需要按照所制定的相应规则进行,即网络爬虫算法。
· 原因很简单,我们可以利用爬虫技术,自动地从互联网中获取我们感兴趣的内容,并将这些数据内容爬取回来,作为我们的数据源,从而进行更深层次的数据分析,并获得更多有价值的信息。
· 在大数据时代,这一技能是必不可少的。
· 只要是浏览器能做的事情,原则上,爬虫都能够做。

爬虫的思路
每一个网页都是一份 HTML文档,全称叫hypertext markup language,是一种文本标记语言

这是一份很有规则的文档写法,打开一个网页,即是通过了 HTTP协议,对一个资源进行了请求,返还你一份 HTML文档,然后浏览器进行文档的渲染,这样,就形成了一个网页。所以,我们只需要模拟浏览器,发送一份请求,获得这份文档,再抽取出我们需要的内容就好。

通用爬虫和聚焦爬虫工作流程

通用搜索引擎的局限性
· 通用搜索引擎所返回的网页里90%的内容无用。
· 图片、音频、视频多媒体的内容通用搜索引擎无能为力。
· 不同用户搜索的目的不全相同,但是返回内容相同

ROBOTS协议
Robots协议:网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。
例如:https://www.taobao.com/robots.txt

浏览器发送HTTP请求的过程 ——浏览器渲染出来的页面和爬虫请求的页面并不一样

23.1 HTTP基本原理

23.1.1 URI与URL

URI 的全称为Uniform Resource Identifier ,即统一资源标志符
URL 的全称为Universal Resource Locator ,即统一资源定位符

举例来说, https://github.com/favicon.ico
是GitHub 的网站图标链接,它是一个URL ,也是一个URI。 即有这样的一个图标资源,我们用URL/URI 来唯一指定了它的访问方式,这其中包括了访问协议https、访问路径(/即根目录)和资源名称favicon.ico 。通过这样一个链接,我们便可以从互联网上找到这个资源,这就是URL/URI。

· URL是URI的子集
· URI 还包括一个子类叫作URN ,它的全称为Universal Resource Name,即统一资源名称。

URN举例, um:isbn:0451450523
指定了一本书的ISBN,可以唯一标识这本书,但是没有指定到哪里定位这本书。
URN用得非常少,所以几乎所有的URI 都是URL,一般的网页链接我们可称为URI也可称为URL,习惯上称为URL。

URI,URL,URN三者的关系:

23.1.2 超文本

· 超文本,其英文名称叫作 hypertext ,我们在浏览器里看到的网页就是超文本解析而成的, 其网页源代码是一系列HTML 代码, 里面包含了一系列标签。

比如 <img> </img> 显示图片, <a> </a> 为超链接等。

· 浏览器解析这些标签后,便形成了我们平常看到的网页,而网页的源代码HTML 就可以称作超文本。
· 在Chrome浏览器中打开任意一个页面,右击任意地方并选择“检查”(或者快捷键F12)即可打开开发者工具,Elements选项卡即可看到当前页的源代码,这些源代码都是超文本。

23.1.3 HTTP 和 HTTPS

· URL 的开头会有http 或https ,这就是访问资源需要的协议类型。
· 其他协议类型:ftp, sftp, smb。在爬虫中,我们抓取的页面通常就是http 或https协议的。
· HTTP 的全称是 Hyper Text Transfer Protocol ,中文名叫作超文本传输协议。HTTP 协议是用于从网络传输超文本数据到本地浏览器的传送协议,它能保证高效而准确地传送超文本文档。
· HTTPS 的全称是 Hyper Text Transfer Protocol over Secure Socket Layer ,是以安全为目标的HTTP通道,简单讲是HTTP 的安全版, 即 HTTP 下加入 SSL 层,简称为 HTTPS 。

如果没有SSL加密,会被浏览器提示不安全
比如 http://www.pudong.gov.cn/shpd/department/019022/
有些网站虽然使用了HTTPS协议,还是会被浏览器提示不安全,比如早期的12306网站,它的CA证书是中国铁道部自行签发的,而这个证书不被CA机构信任。
爬取这样的站点,就需要设置忽略证书的选项,否则会提示SSL 链接错误。

23.1.4 HTTP请求过程


我们在浏览器中输入一个URL ,回车之后便会在浏览器中观察到页面内容。实际上,这个过程是浏览器向网站所在的服务器发送了一个请求,网站服务器接收到这个请求后进行处理和解析,然后返回对应的响应,接着传回给浏览器。
响应里包含了页面的源代码等内容,浏览器再对其进行解析,便将网页呈现了出来。

比如:https://www.baidu.com/,用Chrome 浏览器的开发者模式下的Network 监听组件来做下演示,它可以显示访问当前请求网页时发生的所有网络请求和响应

打开Chrome 浏览器,输入链接 https://www.baidu.com/
右击并选择“检查”项,即可打开浏览器的开发者工具(F12)

第一列 Name :请求的名称,一般会将URL 的最后一部分内容当作名称。
第二 Status :响应的状态码,这里显示为200 , 代表响应是正常的。通过状态码,我们可以判断发送了请求之后是否得到了正常的响应。
第三列 Type : 请求的文梢类型。这里为document ,代表我们这次请求的是一个HTML 文档,内容就是一些HTML 代码。
第四列 Initiator : 请求源。用来标记请求是由哪个对象或进程发起的。
第五列 Size : 从服务器下载的文件和请求的资源大小。如果是从缓存中取得的资源,则该列会显示from cache 。
第六列 Time : 发起请求到获取响应所用的总时间。
第七列 Waterfall:网络请求的可视化瀑布流

我们先观察第一个网络请求,即www. baidu.com 点击这个条目,即可看到更详细的信息

General 部分

Request URL 为请求的URL,
Request Method 为请求的方法,
Status Code 为响应状态码,
Remote Address 为远程服务器的地址和端口,
Referrer Policy 为Referrer 判别策略。

Response HeadersRequest Headers ,这分别代表响应头和请求头。

23.1.5 请求

请求,由客户端向服务端发出,可以分为4 部分内容:请求方法Request Method 、请求的网址Request URL、请求头 Request Headers 、请求体Request Body

  1. 请求方法( Request Method )
    常见的请求方法有两种: GET 和POST
    · 在浏览器中直接输入URL 并回车,这便发起了一个GET 请求,请求的参数会直接包含到URL里。例如,在百度中搜索Python ,这就是一个GET 请求,链接为 https://www.baidu.corn/s?wd= Python ,其中 URL 中问号后面为请求的参数信息,这里参数wd表示要搜寻的关键字。
    · POST 请求大多在表单提交时发起。比如,对于一个登录表单,输入用户名和密码后,点击“ 登录”按钮,这通常会发起一个POST请求,其数据通常以表单的形式传输,而不会体现在URL 中。
    GET 和POST 请求方法有如下区别:
    · GET 请求中的参数包含在URL 里面,数据可以在URL 中看到,而POST 请求的URL 不会包含这些数据,数据都是通过表单形式传输的,会包含在请求体中。
    · 一般来说,登录时,需要提交用户名和密码,其中包含了敏感信息,使用GET 方式请求的话,密码就会暴露在URL 里面,造成密码泄露,所以这里最好以POST 方式发送。上传文件时,由于文件内容比较大,也会选用POST 方式。
    我们平常遇到的绝大部分请求都是GET 或POST 请求,另外还有一些请求方法:
方法 描述
HEAD 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
PUT 从客户端向服务器传送的数据取代指定的文档的内容。
DELETE 请求服务器删除指定的页面。
CONNECT HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
OPTIONS 允许客户端查看服务器的性能。
TRACE 回显服务器收到的请求,主要用于测试或诊断。
PATCH 是对 PUT 方法的补充,用来对已知资源进行局部更新 。
  1. 请求的网址( Request URL )
    请求的网址,即统一资惊定位符URL ,它可以唯一确定我们想请求的资源。

  2. 请求头( Request Headers )
    请求头,用来说明服务器要使用的附加信息,比较重要的信息有Cookie 、Referer 、User-Agent 等。
    一些常用的头信息:
    Accept :请求报头域,用于指定客户端可接受哪些类型的信息。
    Accept-Language :指定客户端可接受的语言类型。
    Accept-Encoding :指定客户端可接受的内容编码。
    Host :用于指定请求资源的主机IP 和端口号,其内容为请求URL 的原始服务器或网关的位置。从HTTP 1. l 版本开始,请求必须包含此内容。
    Referer :此内容用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如做来源统计、防盗链处理等。
    User-Agent :简称UA ,它是一个特殊的字符串头,可以使服务器识别客户使用的操作系统及版本、浏览器及版本等信息。在做爬虫时加上此信息,可以伪装为浏览器;如果不加,很可能会被识别为爬虫。
    Content-Type:也叫互联网媒体类型( Internet Media Type )或者MIME 类型,在HTTP 协议消息头中,它用来表示具体请求中的媒体类型信息。例如,text/html 代表HTML 格式,image/gif代表GIF 图片, app lication/json 代表JSON 类型,更多对应关系可以查看此对照表:http://tool.oschina.neνcommons
    Cookie :也常用复数形式Cookies ,这是网站为了辨别用户进行会话跟踪而存储在用户本地的数据。它的主要功能是维持当前访问会话。
    请求头是请求的重要组成部分,在写爬虫时,大部分情况下都需要设定请求头。
    我们输入用户名和密码成功登录某个网站后,服务器会用会话保存登录状态信息,后面我们每次刷新或请求该站点的其他页面时,会发现都是登录状态,这就是Cookies 的功劳。Cookies 里有信息标识了我们所对应的服务器的会话,每次浏览器在请求该站点的页面时,都会在请求头中加上Cookies 并将其发送给服务器,服务器通过Cookies 识别出是我们自己,并且查出当前状态是登录状态,所以返回结果就是登录之后才能看到的网页内容。

  3. 请求体( Request Body )
    请求体-般承载的内容是 POST 请求中的表单数据,而对于 GET 请求,请求体则为空。
    例如登录猫眼电影https://maoyan.com时捕获到的请求和响应如图所示:

    登录之前,填写了用户名和密码信息,提交时这些内容就会以表单数据的形式提交给服务器, 此时需要注意Request Headers 中指定Content-Typeapplication, x-www-form-urlencoded
    在爬虫中,如果要构造POST 请求,需要使用正确的 Content-Type ,并了解各种请求库的各个参数设置时使用的是哪种 Content-Type , 不然可能会导致 POST 提交后无法正常响应。

Content-Type 提交数据方式
application/x-www-form-urlencoded 表单数据
multipart/dorm-data 表单文件上传
application/json 序列化JSON数据
text/xml XML数据

23.1.6 响应

响应,由服务端返回给客户端,可以分为三部分:响应状态码Response Status Code、响应头Response Headers和响应体Response Body

  1. 响应状态码( Response Status Code )
    在爬虫中,我们可以根据状态码来判断服务器响应状态,如状态码为200 ,则证明成功返回数据, 再进行进一步的处理,否则直接忽略。
状态码 英文名称 中文描述
200 OK 请求成功。一般用于GET与POST请求
404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
500 Internal Server Error 服务器内部错误,无法完成请求

更多状态码可查看:https://www.runoob.com/http/http-status-codes.html

  1. 响应头( Response Headers )
    响应头包含了服务器对请求的应答信息,如 Content-Type 、Server 、Set- Cookie 等。
    一些常用的头信息:
    Date : 标识响应产生的时间。
    Last-Modified : 指定资源的最后修改时间。
    Content-Encoding : 指定响应内容的编码。
    Server : 包含服务器的信息,比如名称、版本号等。
    Content-Type : 文档类型,指定返回的数据类型是什么,如tex t/htm l 代表返回HTML 文档,application/x-javascript 代表返回JavaScript 文件, image/jpeg 则代表返回图片。
    Set-Cookie : 设置Cookies 。响应头中的Set- Cookie 告诉浏览器需要将此内容放在Cookies中, 下次请求携带Cookies 请求。
    Expires: 指定响应的过期时间, 可以使代理服务器或浏览器将加载的内容更新到缓存里。如果再次访问时,就可以直接从缓存中加载, 降低服务器负载,缩短加载时间。

  2. 响应体( Response Body )
    响应的正文数据都在响应体中,比如请求网页时,它的响应体就是网页的 HTML 代码; 请求一张图片时, 它的响应体就是图片的二进制数据。我们做爬虫请求网页后, 要解析的内容就是响应体。
    在做爬虫时,我们主要通过响应体得到网页的源代码、JSON 数据等, 然后从中做相应内容的提取

23.1.7 网页的组成

网页可以分为三大部分一一HTML , CSS 和JavaScript 。如果把网页比作一个人的话, HTML 相当于骨架, JavaScript 相当于肌肉, css 相当于皮肤,三者结合起来才能形成一个完善的网页。

  1. HTML
    HTML 是用来描述网页的一种语言, 其全称叫作 Hyper Text Markup Language ,即超文本标记语言。网页包括文字、按钮、图片和视频等各种复杂的元素,其基础架构就是HTML 。不同类型的元素通过不同类型的标签来表示,如图片用<img> 标签表示, 视频用<video> 标签表示,段落用<p> 标签表示,它们之间的布局又常通过布局标签<div> 嵌套组合而成,各种标签通过不同的排列和嵌套才形成了网页的框架。
    在Chrome 浏览器中打开百度, 有击并选择“检查” 项(或按Fl2 键) ,打开开发者模式, 这时在Elements 选项卡巾即可看到网页的源代码。标签定义的节点元素相互嵌套和组合形成了复杂的层次关系,就形成了网页的架构。
  2. CSS
    CSS ,全称叫作 Cascading Style Sheets ,即层叠样式表。“层叠”是指当在HTML 中引用了数个样式文件,并且样式发生冲突时,浏览器能依据层叠顺序处理。“样式”指网页中文字大小、颜色、元素间距、排列等格式。
    CSS 是目前唯一的网页页面排版样式标准。

大括号前面是一个CSS 选择器,大括号里面是对选择到的节点的样式设置。

  1. JavaScript
    JavaScript ,简称JS ,是一种脚本语言。它的出现使得用户与信息之间不只是一种浏览与显示的关系,而是实现了一种实时、动态、交互的页面功能。HTML 和CSS 配合使用, 提供给用户的只是一种静态信息,缺乏交互性。我们在网页里可能会看到一些交互和动画效果,如下载进度条、提示框、轮播图等,这通常就是JavaScript 的功劳。

23.1.8 节点数及节点之间的关系

在 HTML 中,所有标签定义的内容都是节点,它们构成了一个HTML DOM 树。

23.1.9 选择器

我们知道网页由一个个节点组成, CSS 选择器会根据不同的节点设置不同的样式规则,那么怎样来定位节点呢?

选择器 示例 示例说明
.class .intro 选择所有class="intro"的元素
#id #firstname 选择所有id="firstname"的元素
* * 选择所有元素
element p 选择所有<p>元素
element,element div,p 选择所有<div>元素和<p>元素
element>element div>p 选择所有父级是<div>元素的 <p> 元素
[attribute=value] [target=-blank] 选择所有使用target="-blank"的元素

23.1.10 流程回顾

23.1.11 能抓怎样的数据

基于HTTP 或HTTPS 协议的,只要是这种数据,爬虫都通过它的URL进行抓取。
· 常规网页,它们对应着HTML 代码,抓取的便是HTML 源代码。
· JSON 字符串(其中API 接口大多采用这样的形式),这种格式的数据方便传输和解析,它们同样可以抓取,而且数据提取更加方便。
· 二进制数据,如图片、视频和音频等。利用爬虫,我们可以将这些二进制数据抓取下来,然后保存成对应的文件名。
· 扩展名的文件,如css 、JavaScript 和配置文件等,这些其实也是最普通的文件,只要在浏览器里面可以访问到,就可以将其抓取下来。

23.1.12 JavaScript渲染页面

有时候,我们在用urllib 或requests“抓取网页时,得到的源代码实际和浏览器中看到的不一样。这是一个很常见的问题。现在网页越来越多地采用Ajax 、前端模块化工具来构建,整个网页可能都是由JavaScript 渲染出来的,也就是说原始的HTML 代码就是一个空壳。

< ! OOCTYPE html>
<html>
<head>
<meta charset="UTF-8” >
<title>This is a Oemo</title>
</head>
<body>
<div id=” container” >
</div>
</body>
<script src=” app.js ”></script>
</html>

body 节点里面只有一个id 为container 的节点,但是需要注意在body 节点后引入了app. js,它便负责整个网站的渲染。
在用时lib 或requests 等库请求当前页面时,我们得到的只是这个HTML 代码,它不会帮助我们去继续加载这个JavaScript 文件,这样也就看不到浏览器中的内容了。

因此,使朋基本HTTP 请求库得到的源代码可能跟浏览器中的页面源代码不太一样。 对于这样的情况,我们可以分析其后台Ajax 接口,也可使用Selenium 、Splash 这样的库来实现模拟JavaScript 渲染。

23.2 会话和Cookies

在浏览网站的过程中,我们经常会遇到需要登录的情况,有些页面只有登录之后才可以访问,而且登录之后可以连续访问很多次网站,但是有时候过一段时间就需要重新登录,还有一些网站,在打开浏览器时就向动登录了,而且很长时间都不会失效,这种情况又是为什么? 其实这里面涉及会话 (Session)和 Cookies 的相关知识,

23.2.1 静态网页和动态网页

  1. 静态网页
<!DOCTYPE html>
<html> <head>
<meta charset=” UTF-8">
<title>This is a Demo</title>
</head>
<body>
<div id=” container”>
<div class =”rapper ”〉
<h2 class =”title'’ > Hello World </ h2 >
<p class=”text ”> Hello, this is a paragraph. </ p>
</div>
</div>
</body>
</html>
  1. 动态网页
<!DOCTYPE html>
<html> <head>
<meta charset=” UTF-8">
<title>This is a Demo</title>
</head>
<body>
<div id=” container”>
<div class =”rapper ”〉
<h2 class =”title'’ > Hello World!
<%= request.getParameter("name")%>
</ h2 >
<p class=”text ”> Hello, this is a paragraph. </ p>
</div>
</div>
</body>
</html>

动态解析URL 中参数的变化,关联数据库井动态呈现不同的页面内容。
可能由JSP 、PHP 、Python 等语言编写的

23.2.2 无状态HTTP

HTTP 的无状态是指HTTP 协议对事务处理是没有记忆能力的,也就是说服务器不知道客户端是什么状态。

如果后续需要处理前面的信息,则必须重传,这导致需要额外传递一些前面的重复请求,才能获取后续响应。这给需要用户登录的页面来说,非常棘手。

  1. Cookies
    Cookies 在客户端,也可以理解为浏览器端,有了Cookies ,浏览器在下次访问网页时会自动附带上它发送给服务器,服务器通过识别Cookies 并鉴定出是哪个用户,然后再判断用户是否是登录状态,然后返回对应的响应。
    我们可以理解为Cookies 里面保存了登录的凭证,有了它,只需要在下次请求携带Cookies 发送请求而不必重新输入用户名、密码等信息重新登录了。
    因此在爬虫中,有时候处理需要登录才能访问的页面时,我们一般会直接将登录成功后获取的Cookies 放在请求头里面直接请求,而不必重新模拟登录。
  2. 会话
    会话在服务端,也就是网站的服务器,用来保存用户的会话信息。会话,其本来的含义是指有始有终的一系列动作/消息。比如,打电话时,从拿起电话拨号到挂断电话这中间的一系列过程可以称为一个会话。而在Web 中,会话对象用来存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web 页之间跳转时,存储在会话对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的Web 页时如果该用户还没有会话, 则Web 服务器将自动创建一个会话对象。当会话过期或被放弃后,服务器将终止该会话。

23.2.3 会话维持

23.2.4 代理的基本原理


反爬虫措施: 检测某个IP 在单位时间内的请求次数,如果超过了这个阔值,就会直接拒绝服务
对于爬虫来说,由于爬虫爬取速度过快,在爬取过程中可能遇到同一个IP 访问过于频繁的问题,此时网站就会让我们输入验证码登录或者直接封锁IP ,这样会给爬取带来极大的不便。

使用代理隐藏真实的IP ,让服务器误以为是代理服务器在请求向己。这样在爬取过程中通过不断更换代理,就不会被封锁,可以达到很好的爬取效果。
代理:实现IP 伪装,反反爬虫

23.3 requests

为什么要学习requests,而不是urllib?

  1. requests的底层实现就是urllib。
  2. requests在python2和python3中通用,方法完全一样。
  3. requests简单易用。
  4. requests能够自动帮我们解压(gzip压缩等)网页内容。

作用:发送网络请求,返回响应数据
中文文档API:https://requests-docs-cn.readthedocs.io/zh_CN/latest/_modules/requests/api.html

23.3.1 requests的用法

requests是python实现的简单易用的HTTP库,使用起来比urllib简洁很多因为是第三方库,所以使用前需要cmd安装pip install requests
基本用法: requests.get()用于请求目标网站,类型是一个HTTPresponse类型

get请求示例:

import requestsresponse = requests.get('http://www.baidu.com')
print(response.status_code)  # 打印状态码
print(response.url)  # 打印请求url
print(response.headers)  # 打印头信息
print(response.cookies)  # 打印cookie信息
print(response.text)  # 以文本形式打印网页源码
print(response.content)  # 以字节流形式打印'''
调用get()方法得到一个Response对象,然后分别输出了Response的类型、状态码、响应体的类型、内容以及Cookies 。
'''
r = requests.get('https://www.baidu.com/')  #请求方法为GET方法
print(type(r))                              #Response类型
print(r.status_code)                        #状态码
print (type(r.text))                        #响应体的类型
print(r.text)                               #响应体的内容
print(r.cookies)                            #Cookies
print(r.request.headers)                    #请求头部信息

其他请求示例:

import requestsr = requests.get('http://httpbin.org/get')          #GET方法
r = requests.post('http://httpbin.org/post')        #POST方法
r = requests.put('http://httpbin.org/put')          #PUT方法
r = requests.delete('http://httpbin.org/delete')    #DELETE方法
r = requests.head('http://httpbin.org/get')         #HEAD方法
r = requests.options('http://httpbin.org/get')      #OPTIONS方法

get方法请求:
requests.get(url,headers,params,verify)
url:请求链接字符串
headers:请求头部信息,是一个字典,常用字段:‘user-agent’,‘cookie’
params:链接里需要携带的参数,是一个字典,如果url已经设置,则不需要传递
verify:忽略ssl证书警告
response对象:(requests.get())
response.request:查看请求信息,如:response.request.headers
response.headers:查看相应的头部信息
response.status_code:相应的状态码,200表示正确,400,401,404,500,…都不正常
response.headers[‘Content_Type’]:相应内容的类型,text/html;…json
response.text:相应内容,(html源码)
response.json():相应内容,(json数据)
response.content:相应内容,(音频,视频,图片等)

23.3.2 带参数的get请求

  1. 直接将参数放在url内:
import requests#方式一
r = requests.get('https://www.baidu.com/s?wd=python')
print(r.text)
print(r.request.headers)response = requests.get('http://httpbin.org/get?name=gemey&age=22')
print(response.text)
  1. 先将参数填写在dict中,发起请求时params参数指定为dict
import requests#方式二
data = { 'wd': 'python'}
r = requests.get('https://www.baidu.com/s', params=data)
print(r.text)
print(r.request.headers)data = { 'name': 'tom', 'age': 20}
response = requests.get('http://httpbin.org/get', params=data)
print(response.text)

中文乱码问题:response.encoding = 'utf8'

import requestsdata = { 'wd': '人工智能'}
response = requests.get('https://www.runoob.com/jsp/jsp-form-processing.html')
print(response.text)    #可能会遇到中文乱码问题
response.encoding = 'utf8' #设置响应编码为utf8print(response.text)

23.3.3 JSON解析

import requestsresponse = requests.get('http://httpbin.org/get')
print(response.text)
print(response.json()) # response.json()方法同json.loads(response.text)
print(type(response.json()))
#encoding:utf8
import requests#解析json数据
def parse_json(url, data):response = requests.get(url, params=data) print(response.text)print(response.json())  #如果不是json数据,会报错#增加返回类型的判断
def parse_json2(url, data):response = requests.get(url, params=data)#获取响应头的content_typecontent_type = response.headers.get('Content-Type')if content_type.endswith('json'):print(response.json())elif content_type.startswith('text'):print(response.text)if __name__ == '__main__':url1 = 'http://httpbin.org/get'data1 = {'name': 'tom', 'age': 20}url2 = 'https://www.baidu.com/s'data2 = {'wd': 'python'}parse_json(url1, data1)parse_json2(url2, data2)

23.3.4 简单保存一个二进制文件

二进制内容为response.content

  1. 图片:
import requests
response = requests.get('https://pic4.zhimg.com/80/v2-e3438d2fc003329e1fa3ae4ee94903bf_1440w.jpg?source=1940ef5c')
b = response.content
with open('fengjing.jpg','wb') as f:f.write(b)
  1. 视频:
import requests
response = requests.get('https://vdn.vzuu.com/HD/d3e878bc-e62f-11ec-8f32-2ea37be35c9e-v4_t121-wghOYfkVKm.mp4?disable_local_cache=1&bu=http-1513c7c2&c=avc.4.0&f=mp4&expiration=1654871876&auth_key=1654871876-0-0-06c7423155449d5ae0b5c7c17cd0baec&v=ali&pu=1513c7c2')
b = response.content
with open('fengjing.mp4','wb') as f:f.write(b)
  1. 音频
import requests
response = requests.get('https://gg-sycdn.kuwo.cn/cff8afeee3d82fca6d2776717df44e7f/62a34c6d/resource/n3/12/69/4182461584.mp3')
b = response.content
with open('fengjing.mp3','wb') as f:f.write(b)

23.3.5 response.text和response.content的区别

response.text

类型:str
解码类型:根据HTTP头部对响应的编码做出有根据的推测,推测的文本编码
如何修改编码方式:response.encoding="gbk"

response.content

类型:bytes
解码类型:没有指定
如何修改编码方式:response.content.decode("utf8")

23.3.6 为请求添加头信息

import requestsdef hasetheaders():headers = {}headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'response = requests.get('http://www.baidu.com',headers=headers)print(response.request.headers.get('User-Agent'))def hasnoheaders():response = requests.get('http://www.baidu.com')print(response.request.headers.get('User-Agent'))if __name__ == '__main__':#请求头中的'User-Agent'没有设置hasnoheaders()#请求头中的'User-Agent'设置了操作系统和浏览器的相关信息hasetheaders()

23.3.7 基本代理

23.3.8 带参数的get请求

import requestsheaders = {'Cookie':'','Host':'www.zhihu.com','User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
}r = requests.get('https://www.zhihu.com/people/jackde-jie-jie',headers=headers)
with open('zhihu.txt', 'w', encoding='utf8') as fi:fi.write(r.text)
# print(r.text)

获取cookie

import requests
1
response = requests.get('http://www.baidu.com')
print(response.cookies)
print(type(response.cookies))
for k,v in response.cookies.items():print(k+':'+v)

23.2.9 cookie和session

cookie和session的区别

  1. cookie数据存放在客户的浏览器上,session数据放在服务器上。
  2. cookie不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗考虑到安全应当使用session。
  3. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。
  4. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

处理cookie和session

  1. 带上cookie和session的好处:能够请求到登陆后的页面
  2. 带上cookie和session的弊端:一套cookie和session往往对应一个用户,请求太快;请求次数太多,容易被识别为爬虫;不需要cookie的时候尽量不去使用cookie
    但是有时为了获取登陆的页面,必须发送带有cookie的请求

requests提供了一个sessiion类,来实现客户端和服务器端的会话保持
使用的方法:

  1. 实例化一个session对象
  2. 让session来发送get或post请求
    session=requests.session()
    response=session.get(url,headers)

会话维持

import requestsrequests.get('http://httpbin.org/cookies/set/number/12345')  # 设置cookies
r = requests.get('http://httpbin.org/cookies')  # 获取cookies
print(r.text)  # 没有内容session = requests.Session()  # 创建一个会话
session.get('http://httpbin.org/cookies/set/number/12345')  # 设置cookies
response = session.get('http://httpbin.org/cookies')  # 获取cookies
print(response.text)  # 有内容

23.3.9 证书验证设置

import requests
from requests.packages import urllib3
urllib3.disable_warnings()  # 从urllib3中消除警告
response = requests.get('https://www.12306.cn',verify=False)  # 证书验证设为FALSE
print(response.status_code)
打印结果:200

23.3.10 异常处理

在你不确定会发生什么错误时,尽量使用try…except来捕获异常
所有的requests exception:

import requests
from requests.exceptions import ReadTimeout,HTTPError,RequestExceptiontry:response = requests.get('http://www.baidu.com',timeout=0.5)print(response.status_code)
except ReadTimeout:print('timeout')
except HTTPError:print('httperror')
except RequestException:print('reqerror')

超时异常捕获

import requests
from requests.exceptions import ReadTimeout
from requests.exceptions import ConnectTimeouttry:res = requests.get('http://httpbin.org', timeout=0.1)print(res.status_code)
except (ReadTimeout,ConnectTimeout) as timeout:print(timeout)

23.4 数据提取

简单的来说,数据提取就是从响应中获取我们想要的数据的过程。

23.4.1 数据分类

非结构化的数据:html等
处理方法:正则表达式、xpath

结构化数据:json,xml等
处理方法:转化为python数据类型

23.4.2 JSON数据提取

为什么要学习json
由于把json数据转化为python内建数据类型很简单,所以爬虫中,如果我们能够找到返回json数据的URL,就会尽量使用这种URL,而很多地方也都会返回json

什么是json
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互。

哪里能找到返回json的url

  1. 使用chrome切换到手机页面;
  2. 抓包手机app的软件

具有read()或者write()方法的对象就是类文件对象:f = open(“a.txt”,”r”) f 就是类文件对象

json在数据交换中起到了一个载体的作用,承载着相互传递的数据

json使用注意点
json中的字符串都是用 双引号 引起来的,且必须是双引号,如果不是双引号:

  1. eval:能实现简单的字符串和python类型的转化
  2. replace:把单引号替换为双引号

往一个文件中写入多个json串,不再是一个json串,不能直接读取,这时我们可以一行写一个json串,按照行来读取

23.4.3 正则表达式提取

  1. 知乎爬取
import requests
import re#如果不设置这个headers,知乎会拒绝我们访问
headers = {}headers['User-Agent'] = 'Mozilla/5.0 ' \'(Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 ' \'(KHTML, like Gecko) Version/5.1 Safari/534.50'response = requests.get('https://www.zhihu.com/special/1394398803970056192',headers=headers)
#print(response.text)
pattern = re.compile(r'class="css-49pz06">(.*?)</a>', re.S)
titles = re.findall(pattern , response.text)
print(titles)
print(len(titles))
  1. 抓取代理ip
import requests
import redef get_html(url):heads = {}heads['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'req = requests.get(url, headers=heads)html = req.textreturn htmldef get_ipport(html):regex = r'<td data-title="IP">(.+)</td>'iplist = re.findall(regex, html)regex2 = '<td data-title="PORT">(.+)</td>'portlist = re.findall(regex2, html)regex3 = r'<td data-title="类型">(.+)</td>'typelist = re.findall(regex3, html)sumray = []for i in iplist:for p in portlist:for t in typelist:passpassa = t+','+i + ':' + psumray.append(a)print('高匿代理')print(sumray)return sumrayif __name__ == '__main__':free_proxy_url = 'http://www.kuaidaili.com/free/'   urls = ['https://www.kuaidaili.com/free/inha/{}/'.format(page) for page in range(1,50)]proxys = []for url in urls:res = get_ipport(get_html(url))proxys.append(res)print(proxys)
  1. 博客园数据爬取
#coding=utf-8
'''
标题,时间,阅读量,评论数,推荐数
'''
import requests
import redef get_one_page(url):headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'}response = requests.get(url, headers=headers)if response.status_code == 200:return response.textreturn Nonedef parse_one_page(html):str_text = html.replace("\n","")blogs = re.findall(r'<div class="day"(.*?)编辑\s*</a>',str_text)for blog in blogs:title = re.findall(r'<span>(.+?)</span>', blog)date = re.findall(r'class="dayTitle">.*?>(.*?)</a>',blog)read = re.findall(r'阅读\((.*?)\)',blog)comment = re.findall(r'评论\((.*?)\)',blog)recommend = re.findall(r'推荐\((.*?)\)',blog)print(list(zip(title,date,read,comment,recommend)))strs = '\t'.join(list(zip(title,date,read,comment,recommend))[0]).replace(' ','')with open('博客园数据.txt','a',encoding='utf-8') as f:f.write(strs+'\n')def main():url = 'https://www.cnblogs.com/pinard/default.html'for page in range(1,15):urls = f"{url}?page={page}"html = get_one_page(urls)# print(html)parse_one_page(html)main()
urls = ['https://www.cnblogs.com/pinard/default.html']
#urls = ['https://www.cnblogs.com/pinard/default.html?page={}'.format(page) for page in range(1,15)]
  1. 猫眼数据爬取
import requests
import redef get_one_page(url):headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'}response = requests.get(url, headers=headers)if response.status_code == 200:return response.textreturn None
def parse_one_page(html):index = re.findall(r'<a\s+href=.+?title=\"(\w+)\"\s+data-act',html)print(index)def main():url = 'https://www.maoyan.com/BOARD'html = get_one_page(url)# print(html)parse_one_page(html)main()

23.4.4 动态网站数据抓取

AJAX(Asynchronouse JavaScript And XML)异步JavaScript和XML。过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用Ajax)如果需要更新内容,必须重载整个网页页面。因为传统的在传输数据格式方面,使用的是XML语法。因此叫做AJAX,其实现在数据交互基本上都是使用JSON。使用AJAX加载的数据,即使使用了JS,将数据渲染到了浏览器中,在右键->查看网页源代码还是不能看到通过ajax加载的数据,只能看到使用这个url加载的html代码。

获取ajax数据的方式:

方式 优点 缺点
分析接口 直接可以请求到数据。不需要做一些解析工作,代码量少,性能高。 分析接口比较复杂,特别是一些通过js混淆的接口,要有一定的js功底。容易发现是爬虫
sel eni um 直接模拟浏览器的行为。浏览器能请求到的,使用selenium也能请求到。爬虫更稳定。 代码量多,性能低。

Selenium+chromedriver获取动态数据:
Selenium相当于是一个机器人。可以模拟人类在浏览器上的一些行为,自动处理浏览器上的一些行为,比如点击,填充数据,删除cookie等。chromedriver是一个驱动Chrome浏览器的驱动程序,使用他才可以驱动浏览器。当然针对不同的浏览器有不同的driver。以下列出了不同浏览器及其对应的driver:
Chrome:https://sites.google.com/a/chromium.org/chromedriver/downloads
Firefox:https://github.com/mozilla/geckodriver/releases
Edge:https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
Safari:https://webkit.org/blog/6900/webdriver-support-in-safari-10/

安装Selenium和chromedriver:

  1. 安装Selenium:Selenium有很多语言的版本,有java、ruby、python等。我们下载python版本的就可以了。
    pip install selenium
  2. 安装chromedriver:下载完成后,放到不需要权限的纯英文目录下就可以了。

Selenium和chromedriver:

from selenium import webdriver
# chromedriver的绝对路径
driver_path = r'D:\ProgramApp\chromedriver\chromedriver.exe'
# 初始化一个driver,并且指定chromedriver的路径
driver = webdriver.Chrome(executable_path=driver_path)
# 请求网页
driver.get("https://www.baidu.com/")
# 通过page_source获取网页源代码
print(driver.page_source)

Notes:
如果只是想要解析网页中的数据,那么推荐将网页源代码扔给lxml来解析。因为lxml底层使用的是C语言,所以解析效率会更高。
如果是想要对元素进行一些操作,比如给一个文本框输入值,或者是点击某个按钮,那么就必须使用selenium给我们提供的查找元素的方法。

23.5 Beautiful Soup

Beautiful Soup是什么?
Beautiful Soup(简称BS4)提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。
Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。
BeautifulSoup已成为和lxml、html6lib一样出色的python解释器,为用户灵活地提供不同的解析策略或强劲的速度。
Beautiful Soup不需要考虑编码方式,除非文档没有指定,重新原始编码方式即可。

23.5.1 BS4引入

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_str, 'lxml')
初始化Beautifol Soup 实例化一个bf4对象,相当于一个根节点对象,它的子节点会作为他的实例属性保存起来

这个实例属性是一个tag实例对象:
tag实例对象的主要属性:
string(可以是该节点的文本内容,相当于xpath中的text())
attrs:该节点/标签的属性,是个字典。比如字典里有{‘id’:‘title0’}
该节点的子节点会作为它的实例属性保存起来,如果有同名的子节点,则只会将第一个作为实例属性保存起来

import requests
from bs4 import BeautifulSouphtml_str = """
<html>
<head>
<title id='title0'>demo</title>
</head>
<body>
<p>1111111
</body>
</html>
"""soup = BeautifulSoup(html_str, 'lxml')  #初始化Beautifol Soup 实例化一个bf4对象
print(type(soup))
print(soup.prettify())  # 这个方法可以把要解析的字符串以标准的缩进格式输出
print(soup.title.string)
print(soup.head)
print(soup.p.string)
print(soup.name)
print(soup.body.name)

如何遍历文档树?

  1. 直接子节点:
    .contents :标签的内容 .content属性可以将tag的子节点以列表的方式输出
    .children:返回一个可迭代对象
  2. 所有子孙节点:
    .descendants属性可以对所有tag的子孙节点进行递归循环,和children类似,我们也需要遍历获取其中内容。
  3. 节点内容:
    .string:返回标签里面的内容
    .text:返回标签的文本
from bs4 import BeautifulSouphtml_str = """
<html>
<head>
<title id='title0'>demo</title>
</head>
<body>
<p><span>1111111</span></p>
<p>222222</p>
<p>333333</p>
</body>
</html>
"""soup = BeautifulSoup(html_str, 'lxml')#(1)子节点和子孙节点#直接子节点使用contents属性
print(soup.body.contents)#children属性
print(soup.body.children)
for i , child in enumerate(soup.body.children):print(i, child)#所有子孙节点,descendants
print(soup.body.descendants)
for i, child in enumerate(soup.body.descendants):print(i, child)#(2)父节点和祖先节点
print(soup.title.parent)#直接父节点
print(list(enumerate(soup.title.parents)))#所有的祖先节点#(3)兄弟节点
print("previous_sibling",soup.p.previous_sibling)
print("next_sibling",soup.p.next_sibling.string)
print("previous_siblings",list(soup.p.previous_siblings))
print("next_siblings",soup.p.next_siblings)

23.5.2 BS4选择元素

html_str = """
<html>
<head>
<title id='title0'>demo</title>
</head>
<body>
<p>1111111</p>
<p>2222222</p>
</body>
</html>
"""soup = BeautifulSoup(html_str, 'lxml')  #初始化Beautifol Soup 实例化一个bf4对象
print(type(soup))  # <class 'bs4.BeautifulSoup'>
# print(soup.prettify())  # 这个方法可以把要解析的字符串以标准的缩进格式输出
print(soup.title.string)  # demo
print(soup.head)
'''
<head>
<title id="title0">demo</title>
</head>
'''
print(soup.p.string)  # 1111111  /n
print(soup.name)  # [document]
print(soup.body.name) # body
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)

23.5.3 BS4提取信息

import requests
from bs4 import BeautifulSouphtml_str = """
<html>
<head>
<title id='title0'>demo</title>
</head>
<body>
<p><span>1111111</span></p>
<p>abc</p>
</body>
</html>
"""soup = BeautifulSoup(html_str,'lxml')
#选择节点
title_tag = soup.title  # 重要的数据结构:'bs4.element.Tag'#提取信息
#(1)获取名称
tag_name = title_tag.name
print(tag_name)  # title
#(2)获取属性
#方法1:使用attrs属性
atrr_id = title_tag.attrs['id']
print(atrr_id)  # title0
print(title_tag.attrs)  # {'id': 'title0'}
#方法2:使用方括号[]
atrr_id = title_tag['id']
print(atrr_id)  # title0#(3)获取内容
#单个节点
title_text = title_tag.string
print(title_text)  # demo#嵌套选择
span_tag = soup.p.span
print(span_tag)         # span节点
print(span_tag.name)    # 节点名称
print(span_tag.attrs)   # 节点属性
print(span_tag.string)  # 节点的文本内容

23.5.4 find_all方法选择器

find_all(name,attrs,recursive,text,**kwargs)

  1. 传入True:找到所有的Tagsoup.find_all('b') # 直接找元素相当于name = 'b'
  2. 传入正则表达式:通过正则找
import re
for tag in soup.find_all(re.compile("^b")):print(tag.name)
  1. 传入列表:找a 和 b标签soup.find_all(["a", "b"])
  2. keyword参数(name,attrs)soup.find_all(id='link2')
    如果传入 href 参数,Beautiful Soup会搜索每个tag的”href”属性:soup.find_all(href=re.compile("elsie"))
  3. 通过text参数可以搜索文档中的字符串内容
    soup.find_all(text="Elsie")
    soup.find_all(text=['Tillie','Elsie','Lacie'])
  4. 限定查找个数-limit参数soup.find_all("a",limit=2)
  5. recursive参数:调用tag的 find_all方法,BeautifulSoup会检查当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可是使用参数recursive=Falsesoup.find_all('title',recursive=False)
from bs4 import BeautifulSoup
import rehtml_str = """
<html>
<head>
<title id='title0'>This is title.</title>
<title>This is title also.</title>
</head>
<body>
<p><span>1111111</span></p>
<p class='class0'>2222222</p>
</body>
</html>
"""soup = BeautifulSoup(html_str,'lxml')
print(soup.find(text=re.compile('title')))  # This is title.
from bs4 import BeautifulSoup
import rehtml_str = """
<html>
<head>
<title id='title0'>This is title.</title>
<title>This is title also.</title></head>
<body>
<p><span>1111111</span></p>
<p class='class0'>2222222</p>
</body>
</html>
"""soup = BeautifulSoup(html_str)#find_all(name , attrs , recursive , text , **kwargs)
#(1)name 通过标签名定位
print(soup.find_all(name='p'))  # 等同于soup.body.children 获取根节点下的所有瓶子为p的节点
for p in soup.find_all(name='p'):print(p.string)for span in p.find_all(name='span'):print(span.string)
print(soup.p.span.string)
print(soup.p.string)      #(2)attrs通过属性定位——找到一个节点
print(soup.find_all(attrs={'id':'title0'}))
# [<title id="title0">This is title.</title>]
print(soup.find_all(attrs={'class':'class0'}))
# [<p class="class0">2222222</p>]#(3)text:可用来匹配节点的文本,传入的形式可以是字符串,也可以是正则表达式对象
print(soup.find_all(text=re.compile('title')))
# ['This is title.', 'This is title also.']
  1. 结合节点操作print(soup.find(id='head'.div.div.next_sibling.next_sibling)
  2. find_parents() find_parent()
    find_all() 和 find() 只搜索当前节点的所有子节点,孙子节点等.
    find_parents() 和 find_parent() 用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同,搜索文档搜索文档包含的内容
  3. find_next_siblings() find_next_sibling()
    这2个方法通过 .next_siblings 属性对当 tag 的所有后面解析的兄弟 tag 节点进行迭代,
    find_next_siblings() 方法返回所有符合条件的后面的兄弟节点
    find_next_sibling() 只返回符合条件的后面的第一个tag节点
  4. find_previous_siblings() find_previous_sibling()
    这2个方法通过 .previous_siblings 属性对当前 tag 的前面解析的兄弟 tag 节点进行迭代,
    find_previous_siblings() 方法返回所有符合条件的前面的兄弟节点,
    find_previous_sibling() 方法返回第一个符合条件的前面的兄弟节点
  5. find_all_next() find_next()
    这2个方法通过 .next_elements 属性对当前 tag 的之后的 tag 和字符串进行迭代,
    find_all_next() 方法返回所有符合条件的节点
    find_next() 方法返回第一个符合条件的节点
  6. find_all_previous() 和 find_previous()
    这2个方法通过 .previous_elements 属性对当前节点前面的 tag 和字符串进行迭代,
    find_all_previous() 方法返回所有符合条件的节点,
    find_previous()方法返回第一个符合条件的节点

23.5.5 CSS选择器

这就是另一种与 find_all 方法有异曲同工之妙的查找方法. 写 CSS 时,标签名不加任何修饰,类名前加.,id名前加#在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list

  1. 通过标签名查找 soup.select('a')
  2. 通过类名查找 soup.select('.sister')
  3. 通过 id 名查找 soup.select('#link1')
  4. 组合查找soup.select('p #link1')
  5. 属性查找 soup.select('a[class="sister"]')
  6. 获取内容 get_text()
from bs4 import BeautifulSoup
import rehtml_str = """
<html>
<head>
<title id='title0'>This is title.</title>
<title>This is title also.</title>
</head>
<body>
<p><span>1111111</span></p>
<p class='class0'>2222222</p>
<ul>
<li class ="course">python</li>
<li class ="course">PHP</li>
<li class ="course">Jave</li>
<li class ="course">javescrip</li>
</ul>
</body>
</html>
"""soup = BeautifulSoup(html_str)
print(soup.select('.course'))
print(soup.select('#title0'))
print(soup.select('ul li'))  # 空格表示承接关系
for li in soup.select('ul li'):print(li.string)print("====="*20)
for li in soup.select('ul li'):print(li.get_text())

博客园数据爬取

'''
标题,时间,阅读量,评论数,推荐数
url = 'https://www.cnblogs.com/pinard/default.html'
'''
import requests
import re
from bs4 import BeautifulSoupdef get_one_page(url):headers = {}headers['User-Agent'] = 'Mozilla/5.0 ' \'(Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 ' \'(KHTML, like Gecko) Version/5.1 Safari/534.50'response = requests.get(url,headers=headers)if response.status_code == 200:return response.textreturn Nonedef parse_one_page(html):html = html.replace('\n','')soup = BeautifulSoup(html, 'lxml')blogs = soup.select('div.day')for blog in blogs:title = [blog.select('div.postTitle > a > span')[0].string.strip()]time = blog.find_all(text=re.compile(r'(....年.*月.*日)'))reader_str = blog.find_all(text=re.compile(r'阅读\((\d+)'))#reader_str = blog.select('div.postDesc > span.post-view-count')[0].stringcomments_num = blog.find_all(text=re.compile(r'评论\((\d+)'))recommend_num = blog.find_all(text=re.compile(r'推荐\((\d+)'))print(list(zip(title,time,reader_str,comments_num,recommend_num)))def main():url = 'https://www.cnblogs.com/pinard/default.html'html = get_one_page(url)parse_one_page(html)main()
'''
标题,时间,阅读量,评论数,推荐数
url = 'https://www.cnblogs.com/pinard/default.html'
'''
import requests
import re
from bs4 import BeautifulSoupdef get_one_page(url):headers = {}headers['User-Agent'] = 'Mozilla/5.0 ' \'(Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 ' \'(KHTML, like Gecko) Version/5.1 Safari/534.50'response = requests.get(url,headers=headers)if response.status_code == 200:return response.textreturn Nonedef parse_one_page(html):print()soup = BeautifulSoup(html, 'lxml')blogs = soup.select('.day')for blog in blogs:day = blog.select(".dayTitle a")[0].stringtitle = blog.select('a span')[0].string.strip()abstr = blog.select('.c_b_p_desc')[0].contents[0]desc = blog.select('.postDesc span')view,comment,digg = (re.findall(r'\d+',i.string)[0] for i in desc)data = '\t'.join((title,day,view,comment,digg)).replace(' ','')yield data        def main():url = 'https://www.cnblogs.com/pinard/default.html'html = get_one_page(url)parse_one_page(html)main()

小结

  1. 构建一个soup对象
  2. 定位节点
    2.1 soup.find_all(name),attrs,text
    2.2 soup.tagname
  3. 获取节点的属性信息:tag对象.name(节点名称),tag对象.attrs(属性),tag对象.string(文本)
  4. tag节点对象的用法基本上与我们soup类相同

23.6 IXML

安装:pip install lxml
lxml库:
lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。
lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用XPath语法,来快速的定位特定元素以及节点信息。
from lxml import etree

from lxml import etree# 构建文档树
html_str = """
<html>
<head>
<title id='title0'>demo
</head>
<body>
<p>1111111
</body>
</html>
"""
# 利用html_str创建一个节点树对象
html_tree = etree.HTML(html_str)
print(type(html_tree))  # 输出结果为:lxml.etree._Element
result = etree.tostring(html_tree)  # 自动修正html
print(result.decode('utf8'))

23.6.1 节点与属性

  1. Element类是lxml的一个基础类,大部分XML都是通过Element存储的。可以通过Element方法创建:
from lxml import etreeroot=etree.Element('root')
print(root.tag)  # root
  1. 为root节点添加子节点:
child1 = etree.SubElement(root,'child1')
print(root)  # <Element root at 0x27fc8a43100>
print(etree.tostring(root))  # b'<root><child1/></root>'
  1. XML Element的属性格式为Python的dict。可以通过get/set方法进行设置或获取操作:
root.set('id','123')
id = root.get('id')
print(id)  # 123
  1. 遍历全部属性:
for value,name in root.items():print(value,'\t',name)  # id       123

23.6.2 Xpath

XPath(XML Path Lauguage)是一门在XML文档中查找信息的语言,可用来在XML文档中对元素和属性进行遍历。

  1. 选取节点
表达式 描述
nodename 选取此节点的所有子节点
/ 从根节点选取
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
. 选取当前节点
选取当前节点的父节点
@ 选取属性
  1. 谓语
    谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中。在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式 结果
/bookstore/book[1] 选取属于bookstore子元素的第一个book元素
/bookstore/book[last()] 选取属于bookstore子元素的最后一个book元素
/bookstore/book[last() - 1] 选取属于bookstore子元素的倒数第二个book元素
/bookstore/book[position < 3] 选取最前面的两个属于bookstore元素的子元素的book元素
//title[@lang] 选取所有拥有名为lang的属性title元素
//title[@lang = ‘eng’] 选取所有title元素,且这些元素拥有值为eng的lang属性
/bookstore/book[price > 35.00] 选取bookstore 元素的所有book元素,且其中的price元素的值须大于35.00
/bookstore/book[price > 35.00]/title 选取bookstore元素中的book元素的所有title元素,且其中的price元素的值须大于35.00
//div[contains(@class,“f1”)] 选择div属性包含"f1"的元素
  1. 选取未知节点
    XPath 通配符可用来选取未知的 XML 元素
通配符 描述
* 匹配任何元素节点
@* 匹配任何属性节点
node() 匹配任何类型的节点
  1. Xpath运算符
    与运算符基本一致
运算符 描述 实例 返回值
计算两个节点集 //book丨 //cd 返回所用拥有bookl和cd元素的节点集
from lxml import etree#构建文档树
html_str = """
<html>
<head>
<title id='title0'>demo</title>
</head>
<body>
<p class='p1 clss1 clss2'>1111111</p>
<a>a22</a>
<p class='p1 clss1' id = 'p2'>2222222</p>
<a>aaa</a>
</body>
</html>
"""
# 利用html_str创建一个节点树对象
html_tree = etree.HTML(html_str)#所有节点
all_e = html_tree.xpath('//*')
print(all_e)#指定节点
title_e = html_tree.xpath('//title/text()')
print(title_e)  # ['demo']#子节点
child_e = html_tree.xpath('//body/p')
print(child_e)#第N个子节点
child_e_2 = html_tree.xpath('//body/p[2]')
print(child_e_2)#父节点
parent_e = html_tree.xpath('//title/../*')
print(parent_e)#属性匹配
title_id = html_tree.xpath('//title[@id="title0"]')
print(title_id)#文本获取
title_text = html_tree.xpath('//title[@id="title0"]/text()')
print(title_text)  # ['demo']#属性多值匹配
p1_text = html_tree.xpath('//p[@class="p1"]/text()')
print(p1_text)  # 没有匹配
p1_text = html_tree.xpath('//p[contains(@class, "p1")]/text()')
print(p1_text)  # ['1111111', '2222222']#多属性
p2_text = html_tree.xpath('//p[contains(@class, "p1") and @id="p2"]/text()')
print(p2_text)  # ['2222222']

23.6.3 数据爬取

#coding=utf-8
'''
标题,时间,阅读量,评论数,推荐数
url = 'https://www.cnblogs.com/pinard/default.html'
'''
import requests
import re
from lxml import etreedef get_one_page(url):headers = {}headers['User-Agent'] = 'Mozilla/5.0 ' \'(Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 ' \'(KHTML, like Gecko) Version/5.1 Safari/534.50'response = requests.get(url,headers=headers)if response.status_code == 200:return response.textreturn Nonedef parse_one_page(html):xpath_html = etree.HTML(html)blogs = xpath_html.xpath('//*[@id="mainContent"]/div/div')for blog in blogs:title = blog.xpath('./div[2]/a/span/text()')time = blog.xpath('./div[1]/a/text()')reader_num = blog.xpath('./div[5]/span[1]/text()')comments_num = blog.xpath('./div[5]/span[2]/text()')recommend_num = blog.xpath('./div[5]/span[3]/text()')print(title,time,reader_num,comments_num,recommend_num)def main():url = 'https://www.cnblogs.com/pinard/default.html'html = get_one_page(url)parse_one_page(html)main()

23.7 小结

小结:

  1. re 正则表达式,比较难
  2. lxml xpath表达式:
    一,先实例化一个文档树对象:html=etree.HTML()
    二,调用html.xpath(表达式)
    三,表达式的语法:
    2.1 //div
    2.2 //div/a
    2.3 谓语表达
    //div/a[@name=“a1”] ;
    //div/a[contains(@name, “a1”)] ;
    //div/a[1]
    //div/a[@name=“a1” and contains(@class, “c1”)]
    以上返回的是列表,列表里面是Element对象实例,常用属性:
    Element.text 获取文本内容
    Element.attrib 获取标签的属性字典,比如:Element.attrib[‘href’]
    2.4 //div/a/text() :获取文本内容
  3. bs4
    #1.构建一个soup对象
    #2.定位节点:
    2.1 soup.find_all(name, attrs, text)
    2.2 soup.tagname
    2.3 soup.select(css选择器表达式)
    css选择器表达式:
    2.3.1. .class属性的值
    2.3.2. #id属性的值
    2.3.3. 标签名 子标签名 :div a
    2.3.4.谓语表达式: 标签名[属性名=属性值]
    以上的定位方法返回tag对象/tag对象列表
    #3.获取节点的属性信息:tag对象.name(节点名称), tag对象.attrs(属性字典), tag对象.string(文本))
    #4.tag节点对象的用法基本上与我们soup类相同

23.8 MySQL

MySQL 是一个轻量级的关系型数据库,服务器,没有可视化界面
MySQL 使用标准的sql语言
教程参考:https://www.runoob.com/mysql/mysql-tutorial.html

23.8.1 MySQL的启动与关闭

启动服务:net start mysql
进入mysql操作管理界面:mysql -u root –p
退出mysql操作管理界面:exit;
停止服务:net stop mysql

  1. 系统入口:

  2. 页面入口:Ctrl + Shift + 右键 于空白文档,打开powershell

23.8.2 MySQL数据库的基础操作

显示数据库:show databases;
进入一个数据库:use mysql;
显示库里面所有表:show tables;
查询表中所有数据:select * from user;
查询指定字段的数据:select Host,User from user;

创建数据库:create database spider;
创建表: 表名:blog + 字段名:标题,日期,阅读量,评论数,推荐数
create table blog(title VARCHAR(100) COMMENT'标题',date VARCHAR(20) COMMENT'发布时间',read_num int COMMENT'阅读数量',comment_num int COMMENT'评论数量',recomend_num int COMMENT'推荐数量');

varchar相当于字符串,()内为长度

往表中插入数据:
#插入数据 insert into XGBoost类库使用小结 2019年7月1日 阅读(32912) 评论(119) 推荐(14)
#XGBoost算法原理小结 2019年6月5日 阅读(37969) 评论(181) 推荐(18)
insert into blog VALUES('XGBoost类库使用小结','2019年7月1日',32912,119,14);
insert into blog VALUES('XGBoost算法原理小结','2019年6月5日',37969,181,18);

查表语句 select from:select title,read_num,comment_num from blog;
修改语句 update:UPDATE blog set comment_num=1 where comment_num=119;
删除数据 DELETE :DELETE from blog where comment_num=1

关联查询(多个表查询) left join(左关联) inner join(内关联) right JOIN(右关联)
A inner join B 取交集。
A left join B 取 A 全部,B 没有对应的值为 null。
A right join B 取 B 全部 A 没有对应的值为 null。
select * from student inner join score on student.name = score.name;
select * from student left join score on student.name = score.name;
select * from student right join score on student.name = score.name;

#示例中的数据准备
#创建表
create table student(name VARCHAR(10), age int, weight int);
create table score(name varchar(10), score int, course varchar(10));
#插入数据
insert into student values('susan',10,80);
insert into student values('Tom',10,70);
insert into student values('Tommy',10,70);
insert into score Values('susan',90,'语文');
insert into score Values('susan',90,'英语');
insert into score Values('susan',90,'数学');
insert into score Values('Tom100,'数学');
insert into score Values('Tom',88,'语文');
insert into score Values('Tom',91,'英语');
insert into score Values('Tata',91,'英语');

小结:
1、启动数据库服务:net start mysql
2、连接数据库:mysql -u root -p
3、显示数据库:show databases;
4、使用数据库:use 数据库名称;
5、创建数据库:create database 名称;
6、创建表格:create table 表名(字段名 字段类型);
7、插入数据:insert into 表名 values(字段1的值,字段2的值,…);
8、更新数据:update 表名 set 字段=新值 where 字段名=查询条件;
9、删除数据:delete from 表名;

23.8.3 Python操作MySQL

pip install pymysql

  1. 连接数据库:
    假设当前的 MySQL 运行在本地,用户名为 root ,密码为123456,运行端口为 3306 。这里利用PyMySQL 先连接 MySQL ,然后创建一个新的数据库,名字叫作spider。
#1、创建连接
connect = pymysql.connect(host='localhost',
user='root',
password='123456',
port=3306,
database='spider')
  1. 创建游标:
    调用cursor()方法获得MySQL 的操作游标,利用游标来执行SQL 语句
#2、创建游标
cursor = connect.cursor()
  1. 创建表:
#3、创建表
create_table_sql = '''
create table if not exists blog(title VARCHAR(50),
date VARCHAR(15),
read_num int,
comment_num int,
tuijian int)
'''
cursor.execute(create_table_sql)
  1. 插入数据:
#4、插入数据,方法一
insert_sql = '''insert into blog VALUES('XGBoost类库使用小结','2019年7月1日
',32912,119,14)
'''
cursor.execute(insert_sql)
connect.commit()  # 增,删,改需要commit()

  1. 更新数据:
# 5. 更新
update_sqlstr = '''
update blog2 set recomend_num=16 where title like '%999';'''  # like 模糊匹配
cursor.execute(update_sqlstr)
connect.commit()  # 增,删,改需要commit()
connect.close()
  1. 删除数据
# 6 .删除
delete_sqlstr = 'delete from blog2 where recomend_num=16;'
cursor.execute(delete_sqlstr)
connect.commit()
connect.close()
  1. 查询数据
select_sql = '''
select * from blog
'''
cursor.execute(select_sql)
#data = cursor.fetchone() #查询一条数据
#datas = cursor.fetchall() #查询所有数据
datas = cursor.fetchmany(2) #查询多条数据
print(datas)

23.8.4 博客园爬取

#coding=utf-8
'''
标题,时间,阅读量,评论数,推荐数
url = 'https://www.cnblogs.com/pinard/default.html'
'''
import requests
import re
from bs4 import BeautifulSoup
import pymysqldef get_one_page(url):headers = {}headers['User-Agent'] = 'Mozilla/5.0 ' \'(Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 ' \'(KHTML, like Gecko) Version/5.1 Safari/534.50'response = requests.get(url,headers=headers)if response.status_code == 200:return response.textreturn Nonedef parse_one_page(html):print()soup = BeautifulSoup(html, 'lxml')blogs = soup.select('.day')for blog in blogs:day = blog.select(".dayTitle a")[0].stringtitle = blog.select('a span')[0].string.strip()abstr = blog.select('.c_b_p_desc')[0].contents[0]desc = blog.select('.postDesc span')view,comment,digg = (re.findall(r'\d+',i.string)[0] for i in desc)# data = '\t'.join((title,day,view,comment,digg)).replace(' ','')yield title,day,view,comment,digg
def save2mysql(data):#1、创建连接connect = pymysql.connect(host='localhost',user='root',password='123456',port=3306,database='spider')#2、创建游标 cursor = connect.cursor()insert_sqlstr3 = "insert into blog2 values(%s,%s,%s,%s,%s)"cursor.execute(insert_sqlstr3, data)connect.commit()connect.close()    def main():url = 'https://www.cnblogs.com/pinard/default.html'html = get_one_page(url)parse = parse_one_page(html)for data in parse:save2mysql(data)main()

23.8 Scrapy

Scrapy 是一个为了爬取网站数据,提取结构性数据而编写的应用框架,我们只需要实现少量代码,就能够快速的抓取到数据内容。
Scrapy 使用了 Twisted ['twɪstɪd] (其主要对手是Tornado)异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。

安装(anaconda):使用anaconda直接 conda install scrapy

启动时需改变路径

23.8.1 异步与非阻塞的区别


异步: 调用在发出之后,这个调用就直接返回,不管有无结果
非阻塞: 关注的是程序在等待调用结果(消息,返回值)时的状态,指在不能立刻得到结果之前,该调用不会阻塞当前线程。

23.8.2 爬虫流程

前面的爬虫流程

另一种形式爬虫流程

Scrapy的爬虫流程


1.创建一个scrapy项目scrapy startproject mySpider
2.生成一个爬虫scrapy genspider xxx “xxx.com”
3.提取数据完善spider,使用xpath等方法
4.保存数据pipeline中保存数据

23.8.3 Scrapy 项目文件构成

当创建好一个项目后,会生成几个项目文件

|-- spider. py ---- 爬虫文件夹
|---- _init_. py ---- 默认的爬虫代码文件
|---- myspider. py ---- 具体爬虫代码文件
|-- _init_. py ---- 爬虫项目的初始化文件,用来对项目做初始化工作。
|-- items. py ---- 爬虫项目的数据容器文件,用来定义要获取的数据。
|-- pipelines. py ---- 爬虫项目的管道文件,用来对items中的数据进行进一步的加工处理
|-- middlewares.py ---- 爬虫中间件文件,处理request和reponse等相关配置
|-- setting. py ---- 爬虫项目的设置文件,包含了爬虫项目的设置信息
|-- scrapy. cfg ---- 爬虫项目的配置文件,定义了项目的配置文件路径、部署相关信息等内容

23.8.4 小结

代码流程:

  1. 使用指令创建一个项目
    指令:scrapy startproject mySpider
    1.1方式1 cmd/powershell 命令窗口 输入指令执行(推荐)
    1.2方式2 创建一个main.py文件,使用excute函数执行
    from scrapy.cmdline import execute
    execute([‘scrapy’, ‘startproject’, ‘mySpider’])

  2. 使用指令创建一只spider
    指令:scrapy genspider cnblogs www.cnblogs.com
    2.1方式1 cmd/powershell 命令窗口 输入指令执行(推荐)
    先 cd D:\deepblue\course\NO.202202\code\spider\mySpider\mySpider
    然后再输入指令执行
    2.2方式2 创建一个spidermain.py文件,使用excute函数执行
    在D:\deepblue\course\NO.202202\code\spider\mySpider\mySpider下创建一个spidermain.py文件,
    使用excute函数执行

  3. 修改相关文件:
    3.1 修改items.py,按照模板定义提取的字段
    3.2 修改piplines.py, 按照模板定义数据处理类,需要注意,
    该类要实现process_item方法,如果有多个处理类,就需要return item,
    3.3 修改settings.py文件,放开piplines的注释,设置每个数据处理类的执行优先级,数字越小优先级越高
    3.4 修改spiders文件夹下的蜘蛛文件,比如本项目的cnblogs.py
    按照模板定义一个解析类,实现parse方法,数据传递使用yield item

  4. 启动爬虫:
    指令:scrapy crawl 蜘蛛名
    1.1方式1 cmd/powershell 命令窗口 输入指令执行
    1.2方式2 创建一个main.py文件(跟item.py同级目录),使用excute函数执行(推荐),因为可以打断点检查

[深度学习]Part1 Python学习进阶Ch23爬虫Spider——【DeepBlue学习笔记】相关推荐

  1. [深度学习]Part1 Python学习进阶Ch24图像处理OpenCV(24.1~24.13)——【DeepBlue学习笔记】

    本文仅供学习使用 Python高级--Ch24图像处理OpenCV(24.1~24.13) 24. 图像处理OpenCV 24.1 图像读取.显示.保存 24.1.1 图像的基本操作 24.1.2 摄 ...

  2. 【python教程入门学习】Python零基础入门爬虫项目

    Python入门爬虫项目 这是我的第一个python项目,分享给大家. 需求 我们目前正在开发一款产品其功能大致是:用户收到短信如:购买了电影票或者火车票机票之类的事件.然后app读取短信,解析短信, ...

  3. [深度学习]Part2 数据清洗和特征工程Ch06——【DeepBlue学习笔记】

    本文仅供学习使用 数据清洗和特征工程Ch06 1. 特征工程 1.1 特征工程介绍 1.2 特征预处理 1.2.1 数据清洗 1.2.1.1 数据清洗-预处理 1.2.1.2 数据清洗-格式内容错误数 ...

  4. 几个有趣又值得学习的 python 代码,分享给大家一起学习

    先选取了 几个自己认为值得玩味的 python 代码,希望对正在学习 python 的你有所帮助. 1.类有两个方法,一个是 __new__,一个是 __init__,有什么区别,哪个会先执行呢? 运 ...

  5. [深度学习]Part2 支持向量机(SVM)Ch09-1——【DeepBlue学习笔记】

    本文仅供学习使用 本章需要提前学习拉格朗日乘子法.KKT条件.原问题与对偶问题(优化方法) 支持向量机(SVM)Ch09 1. 支持向量机(Support Vector Machine,1995,SV ...

  6. Python学习_006.Python简单错误如何处理-守破离学习法_程序员修炼手册

    简单错误如何处理: 1.与老师对照 发现错误 2.百度等等搜索 3.最后请教老师或高手 守破离学习法: 守:按照老师要求的内容去学去练 破:发现问题解决问题 展开自己的思路 离:反复发现问题解决问题 ...

  7. python如何进阶提升_Python序列操作之进阶篇

    简介 Python 的序列(sequence)通常指一个可迭代的容器,容器中可以存放任意类型的元素.列表和元组这两种数据类型是最常被用到的序列,python内建序列有六种,除了刚刚有说过的两种类型之外 ...

  8. 基于python语言,使用爬虫和深度学习来预测双色球(二、模型训练与测试)

    在上一篇博文中(基于python语言,使用爬虫和深度学习来预测双色球(一.数据的准备)),我们完成了数据的准备,通过爬虫的方式将2003年至今的每一期的中奖数据保存到了txt文件中,那么我们现在就开始 ...

  9. 自学python要到什么程度-学好深度学习,Python 得达到什么程度?

    如今,网络上的Python机器学习资源纷繁复杂,使得刚入门的小白们眼花缭乱.究竟从哪里开始?如何进行?云栖君给你推荐以下内容,相信读完你就会有自己的答案. "开始",是一个令人激动 ...

最新文章

  1. 记录 android 开发的一个 面试 问题
  2. C# 托管资源和非托管资源
  3. 汇编中ah,al,ax;es,cs,ds,ss;
  4. 卡通驱动项目ThreeDPoseTracker——模型驱动解析
  5. 分析java 线程占用内存_Java线程:保留的内存分析
  6. Linux 查看内存状态
  7. web.xml文件头出错
  8. zabbix4.0 mysql本地数据库迁移到腾讯云数据库
  9. picsart旧版本_picsart 2016旧版本下载-picsart 2016版本下载v5.25.4 安卓中文版-2265安卓网...
  10. PHP安卓获取gpgga,对于GPS模块数据接收和提取的问题分析
  11. SRIO IP的学习与应用
  12. python 等差素数数列
  13. 高配游戏组装电脑配置清单表 2021 组装电脑配置推荐2021
  14. Arranging Wine
  15. 日记侠:知识付费创富没赶上,看别人赚钱你急不急?
  16. 不忘初心:从运维工程师到水果贩子的降级路
  17. STM32驱动HC05蓝牙串口通信模块
  18. js 数据验证(psv)
  19. 四点流程做好商机管理
  20. 《超越平凡的平面设计: 版式设计原理与应用》—空白页面,含义无穷

热门文章

  1. 前端---HTML QQ空间主页制作
  2. 纯干货,linux内存管理——内存管理架构(建议收藏)
  3. MySQL可重复读隔离级别为何没有解决幻读(MVCC原理简介)
  4. 2021巢湖学院高考成绩查询,2021年巢湖学院录取结果查询网址入口及录取结果公布时间...
  5. 伙伴系统之避免碎片--Linux内存管理(十六)
  6. excel中#N/A的解释同比环比基比画四象限图的小tips字符串截取函数substitute的深入理解
  7. 逻辑回归分类器(linear_model.LogisticRegression)
  8. linux chrome 硬件加速,在Chrome上开启硬件加速和预先渲染的方法技巧
  9. 002:Django 模板系统介绍
  10. linux服务器上装r,linux服务器安装R语言及Rstudio server