<p>数学中我们学习了列方程式和解方程式。例如,y=2x方程中的x和y就是变量,代表某个数值:当x=1时,y的值就是2;当x=3时,y的值就是6。</p><p>Java中也有变量的概念,它也代表某个数值。例如,变量age代表年龄值,变量name代表姓名值。如果从计算机存储的角度来说,变量实质上就是内存中的一块数据存储区域,该区域有自己的名称(变量名)和类型(数据类型),Java中的每个变量都必须先声明后使用,该区域的数据可以在同一类型范围内不断变化。</p><p>Java的变量包含三个要素,分别是数据类型、变量名和变量值。数据类型决定了这个变量中要存储的数据值的类型及这块内存的宽度,如存储一个整数10和存储一个小数1.5在内存中所需要的宽度与存储方式是不同的。变量名就是一个标识符,方便在程序中使用。变量值就是这个变量具体存储的值。例如,“int age=18;”这个语句说明了age变量的数据类型是整型int,存储的值是18。</p><h2>3.3.1 变量的声明与使用</h2><p>变量的使用步骤可以具体分为声明、赋值、使用三步,下面是这三个步骤的详细介绍。</p><p><strong>1.声明</strong></p><p>变量的声明相当于向JVM申请一部分指定数据类型大小的内存。不同的数据类型,需要占用的内存大小是不同的。另外,JVM中每字节的内存都有自己的编号,称为内存地址,但是在程序中直接使用内存地址是极其不方便的,因此需要给这部分内存命名,方便在程序中对这部分内存进行访问和使用。变量声明如图3-3所示。<strong><br/></strong></p><p><img src="/uploads/images/20231205/d5c0f2c782059f08a7e653e2a5a1ce04.png" title="1.png" alt=""/></p><p>int是表示整数的数据类型,常见的数据类型说明如表3-3所示。</p><p><img src="/uploads/images/20231205/fb6e1b49024ff4786d26e4436670e6ba.png" title="1.png" alt="" width="624" height="129"/></p><p><strong>2.赋值</strong></p><p>将符号“=”右边的值放到对应的内存中。变量赋值如图3-4所示。<strong><br/></strong></p><p><img src="/uploads/images/20231205/33d61bdd86d24a1cbbc95edae94d9560.png" title="1.png" alt=""/></p><p><strong>3.使用</strong></p><p>所谓使用,是指在变量的作用域内将变量中的值拿出来进行打印、运算、比较等。示例代码:</p><p><strong><img src="/uploads/images/20231205/6eaa449711a16a7e82d8605426c0c43f.png" title="1.png" alt="" width="645" height="176"/></strong></p><p><strong>3.3.2 变量的注意事项</strong></p><p>1.必须先声明再使用</p><p>2.变量必须在初始化后才能使用<br/></p><p>3.变量有作用域,并且在同一个作用域中不可以重复命名</p><p>4.变量的值可以变化,但必须在变量声明的数据类型范围内</p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">-------------------------------------------------------------------------</span></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">学习更多专业Java知识,点此查看:</span></p><p style="text-wrap: wrap;"><a href="https://www.maxiaoke.com/manual/springboot_hxjs.html" target="_self"><span style="text-wrap: nowrap;">《手把手带你学习SpringBoot-零基础到实战》</span></a></p><p style="text-wrap: wrap;"><img src="https://www.maxiaoke.com/uploads/images/20230627/b702bf7780c048d3ca8fc11bee9de654.jpg" title="SpringBoot.jpg" alt=""/></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">-------------------------------------------------------------------------</span></p><p><br/></p>
文章列表
<p>高级编程语言由一系列单词和符号组成,并且能与计算机进行交互,实现逻辑功能。为了让程序员与计算机能够进行更好的交互,会提前给一些单词赋予特殊的含义,这些在程序语言中具有特殊含义的单词叫作关键字。其中有一部分关键字在Java中并没有使用,暂时没有赋予特殊含义,这部分称为保留字。</p><h2>1.Java中的关键字</h2><p>Oracle官网提供的Java语言规范之Java SE 8版——The Java® Language Specification Java SE 8 Edition中列出了所有关键字(含保留字),加起来共有50个单词,如图3-1所示。当然在之后的版本中,根据语法的需要,可能会增加新的关键字,如sealed、record等。</p><p><img src="/uploads/images/20231205/a6e65f8ea202abf6230da0b537539dd4.png" title="1.png" alt="" width="792" height="319"/></p><p>关键字是被Java语言赋予特殊含义,具有特殊用途的字符串(单词),如表3-1所示。关键字有一个特点是所有字母都为小写形式,关键字在后面的学习中会依次展开讲解,学习初期不用强行记忆。</p><p><img src="/uploads/images/20231205/85c5a134d09b6fa45d635e284244f9ed.png" title="1.png" alt="" width="693" height="391"/></p><h2>2.Java中的保留字</h2><p>在关键字中,还有一些单词在Java中没有正式使用,但是为了Java与底层系统(如C语言)的交互,Java保留了一些关键字,称为保留字。目前,保留字有如下两个。</p><p>(1)goto。goto语句在其他语言中叫作“无限跳转”语句。Java语言不再使用goto语句,这是因为goto语句会破坏程序结构。在Java语言中可以通过break、continue和return实现“有限跳转”。</p><p>(2)const。const在其他语言中是声明常量的关键字,在Java语言中使用final方式声明常量。</p><h2>3.Java中的特殊值</h2><p>Oracle官网提供的Java语言规范之Java SE 8版——The Java® Language Specification Java SE 8 Edition中特别说明了三个特殊值:true、false、null。这三个特殊值看起来像是关键字,但实际上是字面量值。后面在给标识符命名时,同样要避开使用特殊值。</p><p><span style="text-wrap: nowrap;">-------------------------------------------------------------------------</span></p><p><span style="text-wrap: nowrap;">学习更多专业Java知识,点此查看:</span></p><p><a href="https://www.maxiaoke.com/manual/springboot_hxjs.html" target="_self"><span style="text-wrap: nowrap;">《手把手带你学习SpringBoot-零基础到实战》</span></a></p><p><img src="/uploads/images/20230627/b702bf7780c048d3ca8fc11bee9de654.jpg" title="SpringBoot.jpg" alt=""/></p><p><span style="text-wrap: nowrap;">-------------------------------------------------------------------------</span></p><p><br/></p>
<p>关系型数据库典型的数据结构是表,数据库是由二维表及其之间的联系所组成的一个数据组织。关系型数据库的优点体现在以下几个方面:</p><p>易于维护:都是使用表结构,格式一致。</p><p>使用方便:SQL语言通用,可用于复杂查询。</p><p>复杂操作:支持SQL,可用于一个表以及多个表之间非常复杂的查询。</p><p>不过关系型数据库也存在以下几个方面的缺点:</p><p>读写性能比较差,尤其是海量数据的高效率读写。</p><p>固定的表结构,灵活度稍微欠缺。</p><p>高并发读写需求,对传统关系型数据库来说,硬盘I/O是一个很大的瓶颈。</p><p>Python作为一种主流的编程语言,提供了对关系型数据库的支持。</p><p>SQLite 3可使用sqlite3模块与Python进行集成。sqlite3模块是由Gerhard Haring编写的。该模块提供了一个与PEP 249描述的DB-API 2.0规范兼容的SQL接口。用户不需要单独安装该模块,因为Python 3.x版本默认自带了该模块。</p><p>要使用数据库,首先需要连接到数据库。调用SQLite3的connect()方法即可连接到指定数据库,其语法格式如下:</p><pre class="brush:as3;toolbar:false"> sqlite3.connect(database [,timeout ,other optional arguments])</pre><p>其中,参数database即为需要连接的目标数据库;参数timeout为指定超时设置;参数other optional arguments为其他参数设置。执行该方法将会连接到指定数据库,如果数据库不存在,就尝试创建一个。</p><p><img src="/uploads/images/20231204/307b38b8fa2e9dc2d2d0b2247c88fd48.png" title="1.png" alt=""/></p><p>以上代码调用connect()方法对指定数据库进行连接,并在成功连接后输出相应内容。将以上代码保存为3-13.py,执行以上代码,其结果将会如图3-13所示,并且会在当前目录下生成名为my_db.db的数据库文件。</p><p>表是构成数据库的基本单元,连接数据库之后,就需要在数据库中创建表。要创建表,首先需要调用connection对象的cursor()方法创建一个游标(cursor)对象。然后调用游标对象的execute()方法执行建表的语句,在数据库中创建表。该方法的语法格式如下:</p><pre class="brush:as3;toolbar:false"> cursor.execute(sql [, optional parameters])</pre><p><img src="/uploads/images/20231205/c25485e30276496850ac2004b7bb0025.png" title="1.png" alt="" width="940" height="273"/></p><p>以上代码调用cursor()方法创建一个游标,然后调用execute()方法执行创建表的SQL语句,最后调用commit()方法提交建表操作。执行以上代码将会在当前my_db.db数据库中创建一个名为USER的表。</p><p>成功创建表之后,就需要向表中插入数据。仍然是调用游标对象的execute()方法执行插入数据的SQL语句,并在执行之后调用commit()方法提交这些操作,就可以实现向表中插入数据。</p><p><img src="/uploads/images/20231205/d2c346fc0ac84a16c400e3c7a82c2cd4.png" title="1.png" alt="" width="928" height="293"/></p><p>以上代码通过execute()方法执行SQL语句向表中插入数据。</p><p>执行遍历表中所有信息的语句即可实现浏览表中记录的功能,同样是执行execute()方法,执行相应的SQL语句即可。</p><pre class="brush:as3;toolbar:false"> import sqlite3 as sql con=sql.connect("my_db.db") c=con.cursor() rows=c.execute("SELECT * FROM USER") for r in rows: print("ID = ", r[0]) print("NAME = ", r[1]) print("ADDRESS = ", r[2]) print("SALARY = ", r[3], "\n") con.close()</pre><p>以上代码通过execute()方法执行遍历所有记录的SQL语句以执行浏览数据的操作,然后通过for遍历所有结果集,并将内容输出。</p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">学习更多专业Python知识,点此查看:</span></p><p style="text-wrap: wrap;"><a href="https://www.maxiaoke.com/manual/py_ssz.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-上》</span></a><span style="text-wrap: nowrap;"> </span><a href="https://www.maxiaoke.com/manual/jz_pys_2.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-下》</span></a></p><p style="text-wrap: wrap;"><img src="https://www.maxiaoke.com/uploads/images/20231204/a64aeb23219a212638811c5bd0f355b0.png" title="py小册封面.png" alt=""/></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p><br/></p>
<p>文件是存储数据的基本载体,对文件的操作几乎是所有编程语言都会涉及的内容。而使用Python进行爬虫操作,进行数据的解析,文件操作也是必不可少的内容。本节就先来学习一下文件的存储。</p><p>3.1.1 TXT文件存储</p><p>TXT文件即文本文件,是一种简单的文件类型,用户可以将数据写入文本文件中,在使用时直接读取文件内容即可。Python支持将数据写入文本文件中,在需要使用时可以使用特定方法直接读取。下面就来详细介绍在Python中如何将数据写入文本文件中,以及如何读取。</p><p>使用Python来读写文件是非常简单的操作。可以调用Python自带的open()函数来打开一个文件,获取到文件句柄,然后通过文件句柄就可以进行各种各样的操作。</p><pre class="brush:as3;toolbar:false"> open(name[, mode[, buffering]])</pre><p>其中,参数name为指定需要打开的文件的名称;参数mode为打开的方式,根据打开方式的不同可以进行不同的操作;参数buffering用于指定打开文件时是否寄存。如果buffering取值为0,就不会有寄存;如果buffering取值为1,访问文件时就会寄存;如果将buffering的值设为大于1的整数,就表明这是寄存区的缓冲大小;如果buffering取负值,寄存区的缓冲大小就为系统默认值。</p><p>调用open()函数打开文件之后,还需要以下file对象的方法来配合实现对文件的读取操作</p><p>下面将通过一组实例说明如何调用open()函数及file对象的方法来实现对文件的读取操作。</p><p>首先将以下文本内容保存为test.txt。</p><p><img src="/uploads/images/20231204/e969f64c3707560b1a068e879a2e5333.png" title="1.png" alt="" width="665" height="270"/></p><p>【示例3-1】读取文件内容</p><p><img src="/uploads/images/20231204/e39e9a7812601a8490e73acf1d5ecbbb.png" title="1.png" alt="" width="668" height="72"/></p><p>以上代码调用open()函数打开文件,然后调用read()方法读取文件内容,并将文件内容输出,最后关闭文件。将以上代码保存为3-1.py,执行该代码的结果如图3-1所示。</p><p><img src="/uploads/images/20231204/ff95394ea4afc4cef9ca91d5ff470ce8.png" title="1.png" alt=""/></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">学习更多专业Python知识,点此查看:</span></p><p style="text-wrap: wrap;"><a href="https://www.maxiaoke.com/manual/py_ssz.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-上》</span></a><span style="text-wrap: nowrap;"> </span><a href="https://www.maxiaoke.com/manual/jz_pys_2.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-下》</span></a></p><p style="text-wrap: wrap;"><img src="https://www.maxiaoke.com/uploads/images/20231204/a64aeb23219a212638811c5bd0f355b0.png" title="py小册封面.png" alt=""/></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p><br/></p>
<p>在进行网络爬虫时,经常会提到“代理”一词,那么究竟什么是代理?代理服务器是如何进行工作的?代理有什么作用?如何对代理服务进行设置?这一节就来解决这些问题。</p><p>2.5.1 基本原理</p><p>首先来看什么是代理。代理实际上指的就是代理服务器(Proxy Server),它的功能是代理网络用户获取网络信息。形象地说,代理服务器就是网络信息的中转站。</p><p>在用户正常请求一个网站时,发送了请求给Web服务器,Web服务器把响应传回给用户。如果设置了代理服务器,实际上就是在本机和服务器之间搭建了一个桥,此时本机不是直接向Web服务器发起请求,而是向代理服务器发起请求,请求会发送给代理服务器,然后由代理服务器再发送给Web服务器,接着由代理服务器把Web服务器返回的响应转发给本机。</p><p>这样用户同样可以正常访问网页,但在这个过程中,Web服务器识别出的真实IP就不再是用户本机的IP了,成功实现了IP伪装,这就是代理的基本原理。</p><p>2.5.2 代理的作用</p><p>(1)代理可以突破自身IP访问限制,访问一些平时不能访问的站点。比如网上常说的翻墙、科学上网之类就是使用了代理技术。</p><p>(2)使用代理可以访问一些单位或团体的内部资源。比如使用的如果是教育网内地址段的免费代理服务器,就可以对教育网开放各类FTP下载、上传,以及各类资料查询、共享等服务。</p><p>(3)使用代理可以提高访问速度。通常代理服务器都会设置一个较大的硬盘缓冲区,当有外界的信息通过时,同时也会将其保存到缓冲区中,当其他用户再访问相同的信息时,则直接从缓冲区中取出信息传给用户,以提高访问速度。</p><p>(4)使用代理可以隐藏真实IP。上网者可以通过代理服务隐藏自己的IP,进而免受攻击。对于爬虫来说,使用代理就是为了隐藏自身IP,防止自身的IP被封锁。因为通常大数据量的访问会引起对方主机的怀疑,从而有可能造成封锁IP的访问权限。</p><p>2.5.3 代理分类</p><p>按照代理不同的分类标准,可以对代理进行不同的分类,既可以根据协议区分,又可以根据其匿名程度区分。</p><p>(1)FTP代理服务器:主要用于访问FTP服务器,一般有上传、下载以及缓存功能,端口一般为21、2121等。</p><p>(2)HTTP代理服务器:主要用于访问网页,一般有内容过滤和缓存功能,端口一般为80、8080、3128等。</p><p>(3)SSL/TLS代理:主要用于访问加密网站,一般有SSL或TLS加密功能(最高支持128位加密强度),端口一般为443。</p><p>(4)RTSP代理:主要用于访问Real流媒体服务器,一般有缓存功能,端口一般为554。</p><p>(5)Telnet代理:主要用于Telnet远程控制(黑客入侵计算机时常用于隐藏身份),端口一般为23。</p><p>(6)POP3/SMTP代理:主要用于POP3/SMTP方式收发邮件,一般有缓存功能,端口一般为110/25。</p><p>(7)SOCKS代理:只是单纯传递数据包,不关心具体协议和用法,所以速度快很多,一般有缓存功能,端口一般为1080。SOCKS代理协议又分为SOCKS 4和SOCKS 5,前者只支持TCP,而后者支持TCP和UDP,还支持各种身份验证机制、服务器端域名解析等。简单来说,SOCK 4能做到的SOCKS 5都可以做到,但SOCKS 5能做到的SOCK 4不一定能做到。</p><p>根据代理的匿名程度,代理可以分为如下类别:</p><p>(1)高度匿名代理:会将数据包原封不动地转发,在服务端看来就好像真的是一个普通的客户端在访问,而记录的IP是代理服务器的IP。</p><p>(2)普通匿名代理:会在数据包上做一些改动,在服务端上有可能发现这是一个代理服务器,也有一定概率追查到客户端的真实IP。代理服务器通常会加入的HTTP头有HTTP_VIA和HTTP_X_FORWARDED_FOR。</p><p>(3)透明代理:不但改动了数据包,还会告诉服务器客户端的真实IP。这种代理除了能用缓存技术提高浏览速度、能用内容过滤提高安全性之外,并无其他显著作用,常见的例子是内网中的硬件防火墙。</p><p>(4)间谍代理:指组织或个人创建的用于记录用户传输的数据,然后进行研究、监控等的代理服务器。</p><p>2.5.4 常见代理设置</p><p>常见的代理设置有以下几种:</p><p>(1)使用网上的免费代理。这种情况最好使用高匿代理,另外可用的代理不多,需要在使用前筛选一下可用代理,也可以进一步维护一个代理池。</p><p>(2)使用付费代理服务。互联网上存在许多代理商,可以付费使用,质量比免费代理好很多,而且速度更快,也更稳定,不过需要支付相应代理服务费。</p><p>(3)使用ADSL拨号。拨一次号换一次IP,稳定性高,是一种比较有效的解决方案。</p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">学习更多专业Python知识,点此查看:</span></p><p style="text-wrap: wrap;"><a href="https://www.maxiaoke.com/manual/py_ssz.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-上》</span></a><span style="text-wrap: nowrap;"> </span><a href="https://www.maxiaoke.com/manual/jz_pys_2.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-下》</span></a></p><p style="text-wrap: wrap;"><img src="https://www.maxiaoke.com/uploads/images/20231204/a64aeb23219a212638811c5bd0f355b0.png" title="py小册封面.png" alt=""/></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p><br/></p>
<p>2.4 会话和Cookies</p><p>Cookie是一小段文本信息,伴随着用户请求和页面在Web服务器和浏览器之间传递。用户每次访问站点时,Web应用程序都可以读取Cookie包含的信息。Cookie的基本工作原理:如果用户再次访问站点上的页面,当该用户输入URL地址时,浏览器就会在本地硬盘上查找与该URL相关联的Cookie。如果该Cookie存在,浏览器就将它与页面请求一起发送到用户请求的站点。Cookie根本的用途是:Cookie能够帮助Web站点保存有关访问者的信息。更简单地说,Cookie是一种保持Web应用程序连续性(执行“状态管理”)的方法。</p><p>什么是会话(Session)?当用户访问站点时,服务器会为该用户创建唯一的会话,会话将一直延续到用户访问结束。</p><p>2.4.1 静态网页和动态网页</p><p>静态网页的网址形式通常是以.htm、.html、.shtml、.xml等为后缀的。静态网页一般来说是最简单的HTML网页,服务器端和客户端是一样的,而且没有脚本和小程序,所以它不能动。在HTML格式的网页上,也可以出现各种动态的效果,如.gif格式的动画、FLASH、滚动字母等,这些“动态效果”只是视觉上的,与后面将要介绍的动态网页是不同的概念。</p><p>静态网页有以下特点:(1)静态网页每个网页都有一个固定的URL,且网页URL以.htm、.html、.shtml等常见形式为后缀,而不含有“?”。</p><p>(2)网页内容一经发布到网站服务器上,无论是否有用户访问,每个静态网页的内容都是保存在网站服务器上的,也就是说,静态网页是实实在在保存在服务器上的文件,每个网页都是一个独立的文件。</p><p>(3)静态网页的内容相对稳定,因此容易被搜索引擎检索。</p><p>(4)静态网页没有数据库的支持,在网站制作和维护方面工作量较大,因此当网站信息量很大时,完全依靠静态网页制作方式比较困难。</p><p>(5)静态网页的交互性较差,在功能方面有较大的限制。</p><p>动态网页是以.asp、.jsp、.php、.perl、.cgi等形式为后缀的,并且在动态网页网址中有一个标志性的符号“?”。动态网页与网页上的各种动画、滚动字幕等视觉上的“动态效果”没有直接关系,动态网页可以是纯文字内容,也可以是包含各种动画的内容,这些只是网页具体内容的表现形式,无论网页是否具有动态效果,采用动态网页技术生成的网页都称为动态网页。动态网页也可以采用静动结合的原则,适合采用动态网页的地方采用动态网页,如果需要使用静态网页,就可以考虑用静态网页的方法来实现,在同一个网站上,动态网页内容和静态网页内容同时存在是很常见的事情。</p><p>动态网页具有以下特点:</p><p>(1)交互性,即网页会根据用户的要求和选择而动态改变和响应。例如,访问者在网页上填写表单信息并提交,服务器经过处理将信息自动存储到后台数据库中,并打开相应提示页面。</p><p>(2)自动更新,即无须手动操作,便会自动生成新的页面,可以大大节省工作量。例如,在论坛中发布信息,后台服务器将自动生成新的网页。</p><p>(3)随机性,即当不同的时间、不同的人访问同一网址时会产生不同的页面效果。例如,登录界面自动循环功能。</p><p>(4)动态网页中的“?”对搜索引擎检索存在一定的问题,搜索引擎一般不可能从一个网站的数据库中访问全部网页,或者出于技术方面的考虑,搜索蜘蛛不会去抓取网址中“?”后面的内容,因此采用动态网页的网站在进行搜索引擎推广时,需要做一定的技术处理才能适应搜索引擎的要求。</p><p>静态网页和动态网页各有特点,网站采用动态网页还是静态网页主要取决于网站的功能需求和网站内容的多少,如果网站功能比较简单,内容更新量不是很大,采用纯静态网页的方式会更简单,如果网站功能复杂,那么最好采用动态网页技术来实现。</p><p>2.4.2 无状态HTTP</p><p>HTTP(Hyper Text Transport Protocol,超文本传输协议)是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,它就必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时,它的应答就较快。</p><p>客户端与服务器进行动态交互的Web应用程序出现之后,HTTP无状态的特性严重阻碍了这些应用程序的实现,毕竟交互是需要承前启后的,简单的购物车程序也要知道用户到底在之前选择了什么商品。于是,两种用于保持HTTP连接状态的技术就应运而生了,一个是Cookie,另一个则是Session。</p><p>Cookie是通过客户端保持状态的解决方案。从定义上来说,Cookie就是由服务器发给客户端的特殊信息,这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息。讲得更具体一些:当用户使用浏览器访问一个支持Cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器;接着,服务器在向客户端回传相应的超文本的同时也会发回这些个人信息,当然这些信息并不是存放在HTTP响应体中的,而是存放于HTTP响应头中的;当客户端浏览器接收到来自服务器的响应之后,浏览器会将这些信息存放在一个统一的位置。对于Windows操作系统而言,我们可以从:</p><pre class="brush:as3;toolbar:false">[系统盘]:\Documents and Settings\[用户名]\Cookies</pre><p>目录中找到存储的Cookie。自此,客户端再向服务器发送请求的时候,都会把相应的Cookie再次发回至服务器。而这次,Cookie信息则存放在HTTP请求头中。</p><p>有了Cookie这样的技术实现,服务器在接收到来自客户端浏览器的请求之后,就能够通过分析存放于请求头的Cookie得到客户端特有的信息,从而动态生成与该客户端相对应的内容。通常,我们可以从很多网站的登录界面中看到“请记住我”这样的选项,如果你勾选了该选项之后再登录,那么在下一次访问该网站的时候就不需要进行重复而烦琐的登录动作了,这个功能就是通过Cookie实现的。</p><p>与Cookie相对的一个解决方案是Session,它是通过服务器来保持状态的。由于Session这个词汇包含的语义很多,因此需要在这里明确一下Session的含义。</p><p>首先,我们通常都会把Session翻译成会话,因此可以把客户端浏览器与服务器之间一系列的交互动作称为一个Session。从这个语义出发,我们会想到Session持续的时间,会提到在Session过程中进行了什么操作,等等。</p><p>其次,Session指的是服务器端为客户端所开辟的存储空间,在其中保存的信息就是用于保持状态的。从这个语义出发,我们会提到往Session中存放什么内容,如何根据键值从Session中获取匹配的内容,等等。</p><p>要使用Session,第一步当然是创建Session了。那么Session在何时创建呢?当然是在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同的创建Session的方法,而在Java中是通过调用HttpServletRequest的getSession方法(使用true作为参数)来创建的。在创建了Session的同时,服务器会为该Session生成唯一的Session ID,而这个Session ID在随后的请求中会被用来重新获得已经创建的Session;在Session被创建之后,就可以调用Session相关的方法往Session中增加内容了,而这些内容只会保存在服务器中,发送到客户端的只有Session ID;当客户端再次发送请求的时候,会将这个Session ID带上,服务器接收到请求之后就会依据Session ID找到相应的Session,从而再次使用。正是这样一个过程,用户的状态就得以保持了。</p><p>综上所述,HTTP本身是一个无状态的连接协议,为了支持客户端与服务器之间的交互,我们需要通过不同的技术为交互存储状态,而这些技术就是Cookie和Session。</p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">学习更多专业Python知识,点此查看:</span></p><p style="text-wrap: wrap;"><a href="https://www.maxiaoke.com/manual/py_ssz.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-上》</span></a><span style="text-wrap: nowrap;"> </span><a href="https://www.maxiaoke.com/manual/jz_pys_2.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-下》</span></a></p><p style="text-wrap: wrap;"><img src="https://www.maxiaoke.com/uploads/images/20231204/a64aeb23219a212638811c5bd0f355b0.png" title="py小册封面.png" alt=""/></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p><br/></p>
<p><strong>2.3.1 爬虫概述</strong></p><p>爬虫实际上就是采集网络上数据的一段程序。把这句话拆分一下,去掉其中的修饰词,就可以看到其实爬虫指的就是一段程序。这段程序的功能就是从网络上采集需要的数据。</p><p>一个爬虫的工作流程如下:</p><p>(1)发起请求。</p><p>(2)获取响应内容。</p><p>(3)解析内容。</p><p>(4)保存数据。</p><p>所以,爬虫就是从请求内容(即数据)到获取响应,接着解析内容,最后显示相应内容或者保持内容的过程。</p><p><strong>2.3.2 能抓取什么样的数据</strong></p><p>通过2.3.1小节的介绍,我们了解到爬虫的主要作用就是采集网络上的数据,那么究竟爬虫能抓取什么样的数据呢?从广义上来说,只要是能请求并且能获取响应的数据都能够被抓取,具体包括以下几类:</p><p>(1)网页文本,如HTML文档、JSON格式文本、XML格式文本等。</p><p>(2)图片文件,如JPEG、PNG等图片文件,获取的是二进制文件,保存为相应格式的图片即可。</p><p>(3)视频文件,如MP4、WMV等视频文件,同样获取的是二进制文件,保存为相应的视频格式即可。</p><p>(4)其他文件。只要是能请求到的文件都能获取,比如MP3文件、Flash文件以及其他各种类型的文件。</p><p><strong>2.3.3 JavaScript渲染页面</strong></p><p>在介绍JavaScript渲染页面之前,我们先来了解一下浏览器渲染页面的过程。HTML源代码由浏览器渲染为一个网页需要以下过程:<strong><br/></strong></p><p>(1)浏览器根据HTML结构生成DOM Tree(节点树)。</p><p>(2)浏览器根据CSS(Cascading Style Sheets,层叠样式表)生成CSSOM(样式表对象模型)。</p><p>(3)将DOM和CSSOM整合,形成RenderTree(渲染树)。</p><p>(4)浏览器根据渲染树开始渲染页面。</p><p>在这个过程中遇到<script>时会执行并阻塞渲染,因为JavaScript有权利改变DOM结构,甚至也能改变对象的CSS样式,所以要等JavaScript脚本执行完毕,之后再继续渲染过程。</p><p>通过以上内容可以了解到,JavaScript在整个页面渲染过程中起着举足轻重的作用,包括JavaScript在页面中的放置位置也十分重要。</p><p>如果JavaScript要对页面中的某个元素进行操作,就必须在这个元素加载之后才能进行,比如下面的代码。</p><p>【示例2-4】JavaScript渲染页面错误演示</p><pre class="brush:html;toolbar:false"><!DOCTYPE html> <html lang="zh-CN"> <head> <title>节点方法的调用</title> </head> <body> <script> obj=document.getElementById("my_div") alert(obj.innerHTML) </script> <div id="my_div">1234567</div> </body> </html></pre><p>以上代码尝试获取页面中层里的内容并以弹出窗口的形式显示其内容。将以上代码保存为2-6.htm,执行代码会发现,只会正常显示网页,但并没有任何弹出窗口。之所以会出现这种情况,就是因为<script>的出现中止了页面的渲染,导致获取不到<script>后面的层里的内容。</p><p>如果将代码修改如下:</p><pre class="brush:html;toolbar:false"><div id="my_div">1234567</div> <script> obj=document.getElementById("my_div") alert(obj.innerHTML) </script></pre><p>也就是将<script>放到要操作层之后,修改后将代码保存为2-7.htm,再次执行代码就会看到弹出窗口的提示,执行结果如图2-10所示。</p><p><img src="/uploads/images/20231204/3d4a5a722fb5d992a7a04d4a7c263974.png" title="1.png" alt="" width="693" height="344"/></p><p><br/></p><p>学习更多专业Python知识,点此查看:</p><p style="text-wrap: wrap;"><a href="https://www.maxiaoke.com/manual/py_ssz.html" target="_self">《剑指Python-初级》</a> <a href="https://www.maxiaoke.com/manual/jz_pys_2.html" target="_self">《剑指Python-高级》</a></p><p><img src="/uploads/images/20231204/a64aeb23219a212638811c5bd0f355b0.png" title="py小册封面.png" alt=""/></p><p><br/></p><p><br/></p>
<p>1.4.1 多线程多线程一般指通过技术手段在具体项目中开启两个或两个以上线程,以一起执行任务。在Python中也是如此,我们可以通过Python提供的线程相关的类库在Python项目中开启多线程,比如使用Threading库等方式,这一点会在本书的高并发篇进行详细介绍。通过开启两个或两个以上线程,计算机可以异步或并发执行Python任务。通过该技术手段,Python任务得到充分执行,可以使CPU可以充分地发挥作用,不再因为等待任务的执行而延长CPU等待执行任务的时间。</p><p>1.4.2 多进程多进程一般指通过技术手段,将同一个项目拆分成不同的进程来一起运行项目的现象。这种技术目前只有一些理论内容,并没有实际的产出,因为这种理论在正常情况下实现起来还是有一定难度的,所以本书不做过多介绍。</p><p>1.4.3 单线程单线程指的是项目自始至终只有一个线程负责执行任务。这种单线程执行任务的方式在比较简单的场景中比较适用,但是,一旦项目具备一定的业务逻辑或者有计算要求,再采用单线程的方式去处理任务就不是很合适了。试想一下,一个线程正在执行一个比较耗时的计算任务,而后续代码需要这个计算任务的结果才能继续向下执行。此时,由于只有一个线程执行,所以不得不等待该线程执行完后才能继续执行后续的业务逻辑,这一等待过程会直接反映到用户层面,即用户在使用该项目时会感知到明显的等待时间,这会给用户带来不好的体验。</p><p>为了避免上述现象出现,我们不得不在项目中采用多线程的方式进行优化,但是,使用多线程来对项目进行优化就一定好吗?</p><p>1.4.4 多线程的优势与不足任何事物都具有两面性,多线程亦是如此。在通过多线程来优化项目时,我们不得不反思使用多线程会不会给项目增加其他的负担。答案是一定会。</p><p>对于CPython虚拟机来说,多线程是通过切换线程上下文而实现的。而每一次线程上下文切换,都会带来一定的时间开销,都需要CPython虚拟机去等待执行,这也是使用多线程处理任务必须要花费的时间成本。除了时间成本,在Python项目中将具体代码优化成线程安全的常用手段还是加锁,而一旦给代码解锁,就会带来线程间对于临界区资源的竞争,一旦存在资源的竞争,线程之间就会等待获取锁,从而获取线程所需的资源,这其中也需要一定的时间成本。如果开发者不懂如何给代码加锁,从而乱用各种锁,这对于代码本身就不是一件正常的事情,大概率也不会实现线程安全,只会让结果变得更糟。</p><p>综上所述,我们只有在具备一定的多线程应用能力之后,才能对Python代码进行优化,这样才能发挥多线程的优势。而实现多线程的每一种技术手段都需要读者潜心钻研、自我总结。</p><p><br/></p><p><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p><span style="text-wrap: nowrap;">学习更多专业Python知识,点此查看:</span></p><p><a href="https://www.maxiaoke.com/manual/py_ssz.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-上》</span></a><span style="text-wrap: nowrap;"> </span><a href="https://www.maxiaoke.com/manual/jz_pys_2.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-下》</span></a></p><p><img src="/uploads/images/20231204/a64aeb23219a212638811c5bd0f355b0.png" title="py小册封面.png" alt=""/></p><p><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p><br/></p>
<p>任何一个具体的Python线程拥有7种不同的状态。这7种不同的状态构成了线程的生命周期。</p><p>● 线程创建状态:该状态表明线程刚刚被创建,还没有被调用或初始化,此时的线程只是一个空的线程对象。</p><p>● 线程就绪状态:在该状态下,初始化一些线程运行所需要的属性和方法,以便被任务调用。</p><p>● 线程运行状态:线程实际运行的状态,即线程一旦被任务调用,就会从线程就绪状态转变为线程运行状态,且线程一旦进入运行状态,就表明已经开始执行任务了。</p><p>● 线程中止状态:当线程在运行状态时,由于任务中止或者人为操作等迫使线程停止运行,线程从运行状态转变为中止状态。转变为中止状态的线程,如果没有人为干预,不会自动执行,除非给线程设定一定的饱和策略或其他可恢复线程执行的策略条件。</p><p>● 线程等待状态:线程等待状态分为无限期等待状态和限期等待状态。无限期等待表示CPU资源被先前的线程抢占,且先前的线程一直不释放CPU资源,导致当前线程无限期等待下去;限期等待表示先前已经抢占到CPU资源的线程,在过了一定时间后会自动释放CPU资源,当前线程只需要等待一定时间即可获取CPU资源。线程运行状态无论转变为无限期等待状态还是转变为限期等待状态,均需要开发者控制,线程无法自动转换。</p><p>● 线程阻塞状态:线程阻塞状态与线程等待状态类似,只不过线程阻塞状态更多地用于表示线程队列的状态,即在线程队列中,等待执行任务的线程均可以被认为是阻塞的。线程阻塞状态需要开发者控制,线程无法自动转换。</p><p>为了更清楚地说明线程状态,以及线程各状态间的转换,笔者画了一张线程状态转换图,如图1-7所示。</p><p><img src="/uploads/images/20231204/8d46b3c346776729b273b14de546e4e8.png" title="1.png" alt="" width="726" height="175"/></p><p>为了更清楚地说明线程状态,以及线程各状态间的转换,笔者画了一张线程状态转换图,如图1-7所示。</p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">学习更多专业Python知识,点此查看:</span></p><p style="text-wrap: wrap;"><a href="https://www.maxiaoke.com/manual/py_ssz.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-上》</span></a><span style="text-wrap: nowrap;"> </span><a href="https://www.maxiaoke.com/manual/jz_pys_2.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-下》</span></a></p><p style="text-wrap: wrap;"><img src="https://www.maxiaoke.com/uploads/images/20231204/a64aeb23219a212638811c5bd0f355b0.png" title="py小册封面.png" alt=""/></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p><br/></p>
<p>在对Python类和对象有了一定了解之后,我们还需要了解进程与线程。对于进程与线程,这里不会局限于Python语言层面,而是从操作系统层面展开介绍。进程与线程是入门Python高并发编程必须掌握的基础知识。</p><p>进程(Process)是计算机中的基础运算单元,是CPU统筹计算机中所有任务的程序实体。CPU通过对不同进程进行调用,协调位于寄存器、运算器以及内存中计算机任务的时间片,使每个计算机任务都能得到合理执行和调用。</p><p>线程(Thread)是计算机任务的具体执行者,是操作系统能够进行运算、调度的最小单位。线程隶属于一个具体的进程。在同一时刻,一个进程可以拥有一个或多个线程,线程是开发者可以直接与计算机CPU或内存进行交互的最小单位。</p><p>在操作系统(泛指Windows系统或Linux系统)中,进程指的是CPU调度的程序实体,线程是具体程序实体的执行者。一个进程可以包含多个线程,但是一个线程只能从属于一个具体的进程。一个线程不能跨进程存在,但是一个进程中的线程可以通过技术手段访问或操作另一个进程中的线程。</p><p>下面通过画图的方式来阐述进程与线程在Python项目中的存在方式,如图1-6所示。</p><p><img src="/uploads/images/20231204/99d6ce1d8ce76d8394cfa4a827e69d69.png" title="1.png" alt=""/></p><p>一个具体的Python项目可以表示为一个进程,即Python项目启动之后,就会在计算机中以一个具体的进程存在,并且由计算机操作系统管理。当启动Python项目时,根据Python虚拟机(或解释器)解析Python语言的规范,Python虚拟机会创建一个专门用于解析Python语言的主线程,接着会创建一个专门用于执行Python语言所定义的任务的工作线程,即一旦Python项目正常运行起来,在计算机中就会存在一个Python主线程和至少一个工作线程。如果是多线程的业务场景,Python就会创建出多个工作线程来并发执行任务。</p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">学习更多专业Python知识,点此查看:</span></p><p style="text-wrap: wrap;"><a href="https://www.maxiaoke.com/manual/py_ssz.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-上》</span></a><span style="text-wrap: nowrap;"> </span><a href="https://www.maxiaoke.com/manual/jz_pys_2.html" target="_self"><span style="text-wrap: nowrap;">《剑指Python-下》</span></a></p><p style="text-wrap: wrap;"><img src="https://www.maxiaoke.com/uploads/images/20231204/a64aeb23219a212638811c5bd0f355b0.png" title="py小册封面.png" alt=""/></p><p style="text-wrap: wrap;"><span style="text-wrap: nowrap;">---------------------------------------------------------------------------------</span></p><p><br/></p>