经典的Python爬虫和网络编程面试题

1、动态加载又对及时性要求很高怎么处理?

Selenium+Phantomjs

尽量不使用 sleep 而使用 WebDriverWait

2、分布式爬虫主要解决什么问题?

(1)ip

(2)带宽

(3)cpu

(4)io

3、什么是 URL?

URL,即统一资源定位符,也就是我们说的网址,统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。

4、python 爬虫有哪些常用技术?

Scrapy,Beautiful Soup, urllib,urllib2,requests

5、简单说一下你对 scrapy 的了解?

scrapy 是一个快速(fast)、高层次(high-level)的基于 python 的 web 爬虫构架。

用来下载、并解析 web 页面, 其 parse->yield item->pipeline 流程是所有爬虫的固有模式。

构造形式主要分spider.pypipeline.py item.py decorator.py middlewares.py setting.py。

6、Scrapy 的优缺点?

(1)优点:scrapy 是异步的

采取可读性更强的 xpath 代替正则强大的统计和 log 系统,同时在不同的 url 上爬行支持 shell 方式,方便独立调试写 middleware,方便写一些统一的过滤器,通过管道的方式存入数据库

(2)缺点:基于 python 的爬虫框架,扩展性比较差

基于 twisted 框架,运行中的 exception 是不会干掉 reactor,并且异步框架出错后是不会停掉其他任务的,数据出错后难以察觉。

7、scrapy 和 request?

(1)scrapy 是封装起来的框架,他包含了下载器,解析器,日志及异常处理,基于多线程, twisted 的方式处理,对于固定单个网站的爬取开发,有优势,但是对于多网站爬取 100 个网站,并发及分布式处理方面,不够灵活,不便调整与括展。

(2)request 是一个 HTTP 库, 它只是用来,进行请求,对于 HTTP 请求,他是一个强大的库,下载,解析全部自己处理,灵活性更高,高并发与分布式部署也非常灵活,对于功能可以更好实现.

8、五层网络模型?

应用层—http ftp dns nfs

传输层—tcp --udp

网络层—ip icmp igmp

链路层—data link

物理层—media

设置 ip 和掩码

<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> Ifconfig eth0 192.168.13.225 netmask 255.255.255.0 </pre>

设置网关

route add default gw 192.168.5.1

9、什么是 2MSL?

2MSL 即两倍的 MSL,TCP 的 TIME_WAIT 状态也称为 2MSL 等待状态,当 TCP 的一端发起主动关闭,在发出最后一个 ACK 包后,即第 3 次握手完成后发送了第四次握手的 ACK包后就进入了 TIME_WAIT 状态,必须在此状态上停留两倍的 MSL 时间,等待 2MSL 时间主要目的是怕最后一个 ACK 包对方没收到,那么对方在超时后将重发第三次握手的 FIN包,主动关闭端接到重发的 FIN 包后可以再发一个 ACK 应答包。在 TIME_WAIT 状态时两端的端口不能使用,要等到 2MSL 时间结束才可继续使用。当连接处于 2MSL 等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置 SO_REUSEADDR 选项达到不必等待 2MSL 时间结束再使用此端口。

10、创建一个简单 tcp 服务器需要的流程?

1.socket 创建一个套接字

2.bind 绑定 ip 和 port

3.listen 使套接字变为可以被动链接

4.accept 等待客户端的链接

5.recv/send 接收发送数据

11、TTL,MSL,RTT?

(1)MSL:报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

(2)TTL:TTL 是 time to live 的缩写,中文可以译为“生存时间”,这个生存时间是由源主机设置初始值但不是存的具体时间,而是存储了一个 ip 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。RFC 793 中规定 MSL 为 2 分钟,实际应用中常用的是 30 秒,1 分钟和 2 分钟等。TTL 与 MSL 是有关系的但不是简单的相等的关系,MSL要大于等于 TTL。

(3)RTT: RTT 是客户到服务器往返所花时间(round-trip time,简称 RTT),TCP 含有动态估算 RTT 的算法。TCP 还持续估算一个给定连接的 RTT,这是因为 RTT受网络传输拥塞程序的变化而变化。

12、常用的反爬虫措施?

1.添加代理

2.降低访问频率

User-Agent动态 HTML 数据加载验证码处理Cookie

13、关于 HTTP/HTTPS 的区别

HTTPS 和 HTTP 的区别:

(1)https 协议需要到 ca 申请证书,一般免费证书很少,需要交费。

(2)http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议

(3)http 和 https 使用的是完全不同的连接方式用的端口也不一样,前者是 80,后者是 443。

(4)http 的连接很简单,是无状态的

(5)HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议 要比http 协议安全

应用场合:

(1)http:适合于对传输速度,安全性要求不是很高,且需要快速开发的应用。如 web 应用,小的手机游戏等等.

(2)https:https 应该用于任何场景!

14、HTTPS 有什么优点和缺点

  • 优点:
  • 1、使用 HTTPS 协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
  • 2、HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,要比 http 协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
  • 3、HTTPS 是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本
  • 缺点:
  • 1.HTTPS 协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用
  • 2.HTTPS 协议还会影响缓存,增加数据开销和功耗,甚至已有安全措施也会受到影响也会因此而受到影响。
  • 3.SSL 证书需要钱。功能越强大的证书费用越高。个人网站、小网站没有必要一般不会用。
  • 4.HTTPS 连接服务器端资源占用高很多,握手阶段比较费时对网站的相应速度有负面影响。
  • 5.HTTPS 连接缓存不如 HTTP 高效。

15、HTTPS 是如何实现安全传输数据的

  • HTTPS 其实就是在 HTTP 跟 TCP 中间加多了一层加密层 TLS/SSL。SSL 是个加密套件,负责对 HTTP 的数据进行加密。TLS 是 SSL 的升级版。现在提到 HTTPS,加密套件基本指的是 TLS。原先是应用层将数据直接给到 TCP 进行传输,现在改成应用层将数据给到TLS/SSL,将数据加密后,再给到 TCP 进行传输。

16、HTTPS 安全证书是怎么来的

如何申请,国内和国外有哪些第三方机构提供安全证书认证。

国内:

  • 沃通(WoSign)
  • 中国人民银行联合 12 家银行建立的金融 CFCA
  • 中国电信认证中心(CTCA)
  • 海关认证中心(SCCA)
  • 国家外贸部 EDI 中心建立的国富安 CA 安全认证中心
  • SHECA(上海 CA)为首的 UCA 协卡认证体系

国外:

  • StartSSL
  • GlobalSign
  • GoDaddy
  • Symantec

16、get 和 post 请求有什么区别

区别:

get:

从指定的服务器中获取数据。

GET 请求能够被缓存

GET 请求会保存在浏览器的浏览记录中

以 GET 请求的 URL 能够保存为浏览器书签

GET 请求有长度限制

GET 请求主要用以获取数据

post:

POST 请求不能被缓存下来

POST 请求不会保存在浏览器浏览记录中

以 POST 请求的 URL 无法保存为浏览器书签

POST 请求没有长度限制

POST 请求会把请求的数据放置在 HTTP 请求包的包体中,POST 的安全性比 GET的高.可能修改变服务器上的资源的请求.

应用场合:

post:

请求的结果有持续性的副作用(数据库内添加新的数据行)

若使用 GET 方法,则表单上收集的数据可能让 URL 过长。

要传送的数据不是采用 7 位的 ASCII 编码。

get:

请求是为了查找资源,HTML 表单数据仅用来帮助搜索。

请求结果无持续性的副作用。

收集的数据及 HTML 表单内的输入字段名称的总长不超过 1024 个字符

HTTP 请求会有哪些信息发送到后台服务器。

请求行 (请求方式、资源路径和 HTTP 协议版本)POST /demo/login HTTP/1.1

请求消息头

消息正文(也叫实体内容) username=xxxx&password=1234

17、描述下 scrapy 框架运行的机制?

(1)从 start_urls 里获取第一批 url 并发送请求,请求由引擎交给调度器入请求队列,获取完毕后,调度器将请求队列里的请求交给下载器去获取请求对应的响应资源,并将响应交给自己编写的解析方法做提取处理:1. 如果提取出需要的数据,则交给管道文件处理;

(2)如果提取出 url,则继续执行之前的步骤(发送 url 请求,并由引擎将请求交给调度器入队列…),直到请求队列里没有请求,程序结束。

18、为什么选择redis 数据库?

①scrapy 是一个 Python 爬虫框架,爬取效率极高,具有高度定制性,但是不支持分布式。而 scrapy-redis 一套基于 redis 数据库、运行在 scrapy 框架之上的组件,可以让scrapy 支持分布式策略,Slaver 端共享 Master 端 redis 数据库里的 item 队列、请求队列和请求指纹集合。

②为什么选择 redis 数据库,因为 redis 支持主从同步,而且数据都是缓存在内存中的,所以基于 redis 的分布式爬虫,对请求和数据的高频读取效率非常高

19、实现模拟登录的方式有哪些?

①使用一个具有登录状态的 cookie,结合请求报头一起发送,可以直接发送 get 请求,访问登录后才能访问的页面。

②先发送登录界面的 get 请求,在登录页面 HTML 里获取登录需要的数据(如果需要的话),然后结合账户密码,再发送 post 请求,即可登录成功。然后根据获取的 cookie信息,继续访问之后的页面。

20、简单介绍下 scrapy 的异步处理

scrapy 框架的异步机制是基于 twisted 异步网络框架处理的,在 settings.py 文件里可以设置具体的并发量数值(默认是并发量 16)。

一.项目问题:

1.你写爬虫的时候都遇到过什么反爬虫措施,你最终是怎样解决的

①通过headers反爬虫:解决策略,伪造headers

②基于用户行为反爬虫:动态变化去爬取数据,模拟普通用户的行为, 使用IP代理池爬取或者降低抓取频率,或 通过动态更改代理ip来反爬虫

③基于动态页面的反爬虫:跟踪服务器发送的ajax请求,模拟ajax请求,selnium

和phtamjs。或 使用selenium + phantomjs 进行抓取抓取动态数据,或者找到动态数据加载的json页面。

④验证码 :使用打码平台识别验证码

⑤数据加密:对部分数据进行加密的,可以使用selenium进行截图,使用python自带的pytesseract库进行识别,但是比较慢最直接的方法是找到加密的方法进行逆向推理。

2.你写爬虫的时候 使用的什么框架 选择这个框架的原因是什么?

scrapy。

优势:

  • 可以实现高并发的爬取数据, 注意使用代理;
  • 提供了一个爬虫任务管理界面, 可以实现爬虫的停止,启动,调试,支持定时爬取任务;
  • 代码简洁

劣势:

  • 可扩展性不强。

②整体上来说: 一些结构性很强的, 定制性不高, 不需要太多自定义功能时用pyspider即可, 一些定制性高的,需要自定义一 些 功能时则使用Scrapy。

二.scrapy框架专题部分(很多面试都会涉及到这部分)

1.请简要介绍下scrapy框架。

scrapy 是一个快速(fast)、高层次(high-level)的基于 python 的 web 爬虫构架,用于抓取web站点并从页面中提取结构化的数据。scrapy 使用了 Twisted异步网络库来处理网络通讯

2.为什么要使用scrapy框架?scrapy框架有哪些优点?

  • 它更容易构建大规模的抓取项目
  • 它异步处理请求,速度非常快
  • 它可以使用自动调节机制自动调整爬行速度
  • 提供了一个爬虫任务管理界面, 可以实现爬虫的停止,启动,调试,支持定时爬取任务;
  • 代码简洁

3.scrapy框架有哪几个组件/模块?简单说一下工作流程。

Scrapy Engine:这是引擎,负责Spiders、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等等!(像不像人的身体?)

Scheduler(调度器): 它负责接受引擎发送过来的requests请求,并按照一定的方式进行整理排列,入队、并等待Scrapy Engine(引擎)来请求时,交给引擎。

Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spiders来处理,

Spiders:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),

Item Pipeline:它负责处理Spiders中获取到的Item,并进行处理,比如去重,持久化存储(存数据库,写入文件,总之就是保存数据用的)

Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件

Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spiders中间‘通信‘的功能组件(比如进入Spiders的Responses;和从Spiders出去的Requests)

工作流程:

数据在整个Scrapy的流向:

程序运行的时候,

引擎:Hi!Spider, 你要处理哪一个网站?

Spiders:我要处理23wx.com

引擎:你把第一个需要的处理的URL给我吧。

Spiders:给你第一个URL是XXXXXXX.com

引擎:Hi!调度器,我这有request你帮我排序入队一下。

调度器:好的,正在处理你等一下。

引擎:Hi!调度器,把你处理好的request给我,

调度器:给你,这是我处理好的request

引擎:Hi!下载器,你按照下载中间件的设置帮我下载一下这个request

下载器:好的!给你,这是下载好的东西。(如果失败:不好意思,这个request下载失败,然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载。)

引擎:Hi!Spiders,这是下载好的东西,并且已经按照Spider中间件处理过了,你处理一下(注意!这儿responses默认是交给def parse这个函数处理的)

Spiders:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,这是我需要跟进的URL,将它的responses交给函数 def xxxx(self, responses)处理。还有这是我获取到的Item。

引擎:Hi !Item Pipeline 我这儿有个item你帮我处理一下!调度器!这是我需要的URL你帮我处理下。然后从第四步开始循环,直到获取到你需要的信息,

注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的URL,Scrapy会重新下载。)

以上就是Scrapy整个流程了。

 

官方语言版本:

流程

1.引擎打开一个域名,蜘蛛处理这个域名,并让蜘蛛获取第一个爬取的URL。

2.引擎从蜘蛛那获取第一个需要爬取的URL,然后作为请求在调度中进行调度。

3.引擎从调度那获取接下来进行爬取的页面。

4.调度将下一个爬取的URL返回给引擎,引擎将他们通过下载中间件发送到下载器。

5.当网页被下载器下载完成以后,响应内容通过下载中间件被发送到引擎。

6.引擎收到下载器的响应并将它通过蜘蛛中间件发送到蜘蛛进行处理。

7.蜘蛛处理响应并返回爬取到的项目,然后给引擎发送新的请求。

8.引擎将抓取到的项目发送给项目管道,并向调度发送请求。

系统重复第二步后面的操作,直到调度中没有请求,然后断开引擎与域之间的联系

4.scrapy的去重原理(指纹去重到底是什么原理)

需要将dont_filter设置为False开启去重,默认是False;

对于每一个url的请求,调度器都会根据请求的相关信息加密得到一个指纹信息,并且将指纹信息和set()集合中的指纹信息进行比对,如果set()集合中已经存在这个数据,就不在将这个Request放入队列中。如果set()集合中没有,就将这个Request对象放入队列中,等待被调度。

5.scrapy中间件有几种类,你用过哪些中间件*

scrapy的中间件理论上有三种:

Scheduler Middleware,Spider Middleware,Downloader Middleware,

在应用上一般有以下两种:

1.爬虫中间件Spider Middleware

主要功能是在爬虫运行过程中进行一些处理.

2.下载器中间件Downloader Middleware

主要功能在请求到网页后,页面被下载时进行一些处理.

使用

1.Spider Middleware有以下几个函数被管理:

process_spider_input 接收一个response对象并处理,

位置是Downloader–>process_spider_input–>Spiders(Downloader和Spiders是scrapy官方结构图中的组件)

process_spider_exception spider出现的异常时被调用

process_spider_output 当Spider处理response返回result时,该方法被调用

process_start_requests 当spider发出请求时,被调用

位置是Spiders–>process_start_requests–>Scrapy Engine(Scrapy Engine是scrapy官方结构图中的组件)

2.Downloader Middleware有以下几个函数被管理

- process_request  request通过下载中间件时,该方法被调

- process_response 下载结果经过中间件时被此方法处理

- process_exception 下载过程中出现异常时被调用

6.scrapy中间件在哪里起的作用

1.爬虫中间件Spider Middleware:主要功能是在爬虫运行过程中进行一些处理.爬虫发起请求request的时候调用,列如更换修改代理ip,修改UA,

2.下载器中间件Downloader Middleware:主要功能在请求到网页后,页面被下载时进行一些处理.浏览器返回响应response的时候调用,无效的数据,特殊情况进行重试

三.代理问题:

1.为什么会用到代理

一些网站会有相应的反爬虫措施,例如很多网站会检测某一段时间某个IP的访问次数,如果访问频率太快以至于看起来不像正常访客,它可能就会会禁止这个IP的访问。所以我们需要设置一些代理服务器,每隔一段时间换一个代理,就算IP被禁止,依然可以换个IP继续爬取。

2.代理怎么使用(具体代码, 请求在什么时候添加的代理)

  1. 可以使用urllie中request的Proxy Handler来设置代理ip

一般urllib使用代理ip的步骤如下:

    • 设置代理地址
    • 创建Proxyhandler
    • 创建Opener

④ 安装Opener

from urllib import request,error#构建两个代理Handler,一个有IP,一个没有IPproxy = {'http':'206.125.41.135:80'} httpproxy_handler = request.ProxyHandler(proxy) nullproxy_handler = request.ProxyHandler()#定义一个代理开关proxySwitch = False#通过reqest.build_opener()放法使用这些代理Handler对象,创建自定义opener对象 #根据代理开关是否打开,使用不同的代理模式if proxySwitch:     opener = request.build_opener(httpproxy_handler)else:     opener = request.build_opener(nullproxy_handler) url = "http://www.baidu.com"#安装opnerrequest.install_opener(opener) rsp = request.urlopen(url)print(rsp.status)

2,使用requets代理

import requests# 根据协议类型,选择不同的代理proxies = {     "http": "http://12.34.56.79:9527",     "https": "http://12.34.56.79:9527"} response = requests.get("http://www.baiodu.com",proxies=proxies)print(response.status_code)

3.代理失效了怎么处理

事先用检测代码检测可用的代理,每隔一段时间更换一次代理,如果出现302等状态码,

则立即更换下一个可用的IP。

from urllib import requestdef ProxySpider(url,proxy_ip,headers):     #将代理IP及其协议载入ProxyHandler赋给一个opener_support变量     opener_support = request.ProxyHandler({"http":proxy_ip})     #将opener_support载入build_opener方法,创建opener     opener = request.build_opener(opener_support)     request.install_opener(opener)     req=request.Request(url,headers=headers)     rsp = request.urlopen(req).read()     return rsp

四.验证码处理:

1.登陆验证码处理

  • 图片验证码:先将验证码图片下载到本地,然后使用云打码识别;
  • 滑动验证码:使用selenium模拟人工拖动,对比验证图片的像素差异,找到滑动的位置然后获取它的location和size,然后 top,bottom,left,right = location['y'] ,location['y']+size['height']+ location['x'] +size['width'] ,然后截图,最后抠图填入这四个位置就行。

2.爬取速度过快出现的验证码处理

设置setting.py中的DOWNLOAD_DELAY,降低爬取速度;

用xpath获取验证码关键字,当出现验证码时,识别验证码后再继续运行。

3.如何用机器识别验证码**

对接打码平台

  • 对携带验证码的页面数据进行抓取
  • 将页面中的验证码进行解析, 将验证码图片下载到本地
  • 将验证码图片提交给打码平台进行识别, 返回识别后的结果

云打码平台使用:

在官网中进行普通用户和开发者用户注册

登录开发者用户:

a) 示例代码下载:开发文档 --> 调用示例及最新DLL --> PythonHTTP示例下载

b) 创建一个软件:我的软件 --> 添加新的软件(后期会使用该软件的秘钥和id)

使用示例代码中的示例代码对保存本地的验证码进行识别

 

五.模拟登陆问题:

1.模拟登陆流程

1. 加载浏览器driver; 获取登录页面; 使用css选择器或者xpath找到账号和密码输入框,

       并发送账号和密码;

2. 如果出现验证码则需要先识别验证码,在模拟输入验证码或者模拟鼠标拖动;

3. 使用css选择器或者xpath找到登录按钮,使用click模拟点击;

2.cookie如何处理

1. 采用selenium自动登录获取cookie,保存到文件;

2. 读取cookie,比较cookie的有效期,若过期则再次执行步骤1;

3. 在请求其他网页时,填入cookie,实现登录状态的保持;

3.如何处理网站传参加密的情况**

加密的三种情况:

1. 加密+访问次数限制+每个页面相关信息的条目需要点详情进行二次请求;

2. 复杂的加密算法进行参数+时间戳+sig值,后台进行 参数+时间限制;

3. 定时同步cookie+每个界面一个cookie。

破解方法:

1. 使用selenium模拟点击获取详情页面;

2. 获取其相应的api接口,GET接口URL,获取它的json表格内容;

3. 反向分析网页JS加载内容;

六.分布式

1.什么是分布式

需要计算的数据量大,任务多,一台机器搞不定或者效率极低,需要多台机器共同协作(而不是孤立地各做各的,所以需要通信),最后所有机器完成的任务汇总在一起,完成大量任务.

将一个项目拷贝到多台电脑上,同时爬取数据

分布式爬虫则是将多台主机组合起来,共同完成一个爬取任务,这将大大提高爬取的效率。

记住爬虫的本质是网络请求和数据处理,如何稳定地访问网页拿到数据,如何精准地提取出高质量的数据才是核心问题

2.分布式原理

①分布式爬虫主要由主机与从机组成,我们把自己的核心服务器(主机)称为 master,而把用于跑爬虫程序的机器(从机)称为 slave。

②我们首先给爬虫一些start_urls,spider 最先访问 start_urls 里面的 url,再根据我们的 parse 函数,对里面的元素、或者是其他的二级、三级页面进行抓取。而要实现分布式,只需要在这个starts_urls里面做文章就行了。进一步描述如下:

  • master 产生 start_urls,url 会被封装成 request 放到 redis 中的spider:requests,总的 scheduler 会从这里分配 request,当这里的 request 分配完后,会继续分配start_urls 里的 url。

②slave 从 master 的 redis 中取出待抓取的 request,下载完网页之后就把网页的内容发送回 master 的redis,key 是 spider:items。scrapy 可以通过 settings 来让 spider爬取结束之后不自动关闭,而是不断的去询问队列里有没有新的 url,如果有新的 url,那么继续获取 url并进行爬取,所以这一过程将不断循环。

master 里的 reids 还有一个 key 是 “spider:dupefilter” 用来存储抓取过的 url 的fingerprint(使用哈希函数将url运算后的结果),防止重复抓取,只要 redis 不清空,就可以进行断点续爬。

scrapy实现分布式抓取简单点来说可以借助scrapy_redis类库来实现。

在分布式爬取时,会有master机器和slave机器,其中,master为核心服务器,slave为具体的爬虫服务器。

我们在master服务器上搭建一个redis数据库,并将要抓取的url存放到redis数据库中,所有的slave爬虫服务器在抓取的时候从redis数据库中去链接,由于scrapy_redis自身的队列机制,slave获取的url不会相互冲突,然后抓取的结果最后都存储到数据库中。master的redis数据库中还会将抓取过的url的指纹存储起来,用来去重。相关代码在dupefilter.py文件中的request_seen()方法中可以找到。

通过重写scheduler和spider类,实现了调度、spider启动和redis的交互。实现新的dupefilter和queue类,达到了判重和调度容器和redis的交互,因为每个主机上的爬虫进程都访问同一个redis数据库,所以调度和判重都统一进行统一管理,达到了分布式爬虫的目的。

3.分布式如何判断爬虫已经停止了

1 spider.getStatus();//获取爬虫状态

2 spider.getStatus().equals(Spider.Status.Init);//运行中

4.分布式的去重原理

分布式去重问题:

Duplication Filter:

Scrapy中用集合实现这个request去重功能,Scrapy中把已经发送的request指纹放入到一个集合中,把下一个request的指纹拿到集合中比对,如果该指纹存在于集合中,说明这个request发送过了,如果没有则继续操作。这个核心的判重功能是这样实现的:

1 def request_seen(self, request): 2 # self.request_figerprints 就是一个指纹集合 3 fp = self.request_fingerprint(request) 4 5 # 这就是判重的核心操作 6 if fp in self.fingerprints: 7 return True 8 self.fingerprints.add(fp) 9 if self.file: 10 self.file.write(fp + os.linesep)

在scrapy-redis中去重是由Duplication Filter组件来实现的,它通过redis的set 不重复的特性,巧妙的实现了Duplication Filter去重。scrapy-redis调度器从引擎接受request,将request的指纹存⼊redis的set检查是否重复,并将不重复的request push写⼊redis的 request queue。

引擎请求request(Spider发出的)时,调度器从redis的request queue队列⾥里根据优先级pop 出⼀个request 返回给引擎,引擎将此request发给spider处理。

5、总结

1、最后总结一下scrapy-redis的总体思路:这套组件通过重写scheduler和spider类,实现了调度、spider启动和redis的交互。

2、实现新的dupefilter和queue类,达到了判重和调度容器和redis     的交互,因为每个主机上的爬虫进程都访问同一个redis数据库,   所以调度和判重都统一进行统一管理,达到了分布式爬虫的目的。

3、当spider被初始化时,同时会初始化一个对应的scheduler对象,  这个调度器对象通过读取settings,配置好自己的调度容器queue 和判重工具dupefilter。

4、每当一个spider产出一个request的时候,scrapy引擎会把这个      reuqest递交给这个spider对应的scheduler对象进行调度,     scheduler对象通过访问redis对request进行判重,如果不重复就          把他添加进redis中的调度器队列里。当调度条件满足时,scheduler     对象就从redis的调度器队列中取出一个request发送给spider,  让他爬取。

5、当spider爬取的所有暂时可用url之后,scheduler发现这个spider           对应的redis的调度器队列空了,于是触发信号spider_idle,  spider收到这个信号之后,直接连接redis读取start_urls池,拿   去新的一批url入口,然后再次重复上边的工作。

七.数据存储和数据库问题:

1.关系型数据库和非关系型数据库的区别

详情请点击:https://blog.csdn.net/hzp666/article/details/79168675

2.爬下来数据你会选择什么存储方式,为什么

Redis基于内存,读写速度快,也可做持久化,但是内存空间有限,当数据量超过内存空间时,需扩充内存,但内存价格贵;

MySQL基于磁盘,读写速度没有Redis快,但是不受空间容量限制,性价比高;

大多数的应用场景是MySQL(主)+Redis(辅),MySQL做为主存储,Redis用于缓存,加快访问速度。需要高性能的地方使用Redis,不需要高性能的地方使用MySQL。存储数据在MySQL和Redis之间做同步;

MongoDB 与 MySQL 的适用场景:

MongoDB 的适用场景为:数据不是特别重要(例如通知,推送这些),数据表结构变化较为频繁,数据量特别大,数据的并发性特别高,数据结构比较特别(例如地图的位置坐标),这些情况下用 MongoDB , 其他情况就还是用 MySQL ,这样组合使用就可以达到最大的效率。

今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的访问和抓取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL数据库的发展也却能很好的处理这些大的数据。

3.各种数据库支持的数据类型,和特点

MongoDB

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

它是一个内存数据库,数据都是放在内存里面的。

对数据的操作大部分都在内存中,但 MongoDB 并不是单纯的内存数据库。

MongoDB 是由 C++ 语言编写的,是一个基于分布式文件存储的开源数据库系统。

在高负载的情况下,添加更多的节点,可以保证服务器性能。

MongoDB 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

优点:

1.性能优越:快速!在适量级的内存的 MongoDB 的性能是非常迅速的,它将热数据存储在物理内存中,使得热数据的读写变得十分快,

2.高扩展:第三方支持丰富(这是与其他的 No SQL 相比,MongoDB 也具有的优势)

3.自身的 Failover 机制!

4.弱一致性(最终一致),更能保证用户的访问速度

5.文档结构的存储方式,能够更便捷的获取数据: json 的存储格式

6.支持大容量的存储,内置 GridFS

7.内置 Sharding

8.MongoDB支持各种编程语言:RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。

MongoDB 缺点:

主要是无事物机制!

① MongoDB 不支持事务操作(最主要的缺点)

② MongoDB 占用空间过大

③ MongoDB 没有如 MySQL 那样成熟的维护工具,这对于开发和IT运营都是个值得注意的地方

redis

Redis 一个内存数据库,通过 Key-Value 键值对的的方式存储数据。由于 Redis 的数据都存储在内存中,所以访问速度非常快,因此 Redis 大量用于缓存系统,存储热点数据,可以极大的提高网站的响应速度。

1、Redis优点

(1)支持数据的持久化,通过配置可以将内存中的数据保存在磁盘中,Redis 重启以后再将数据加载到内存中;

(2)支持列表,哈希,有序集合等数据结构,极大的扩展了 Redis 用途;

(3)原子操作,Redis 的所有操作都是原子性的,这使得基于 Redis 实现分布式锁非常简单;

(4)支持发布/订阅功能,数据过期功能;

Redis的数据类型

Redis通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

mysql

MySQL是关系型数据库。

MySQL是一种关系数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。

MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择 MySQL 作为网站数据库。

系统特性

1. [2] 使用 C和 C++编写,并使用了多种编译器进行测试,保证了源代码的可移植性。

2.支持 AIX、FreeBSD、HP-UX、Linux、Mac OS、NovellNetware、OpenBSD、OS/2 Wrap、Solaris、Windows等多种操作系统

3.为多种编程语言提供了 API。这些编程语言包括 C、C++、Python、Java、Perl、PHP、Eiffel、Ruby,.NET和 Tcl 等。

4.支持多线程,充分利用 CPU 资源。

5.优化的 SQL查询算法,有效地提高查询速度。

6.既能够作为一个单独的应用程序应用在客户端服务器网络环境中,也能够作为一个库而嵌入到其他的软件中。

7.提供多语言支持,常见的编码如中文的 GB 2312、BIG5,日文的 Shift_JIS等都可以用作数据表名和数据列名。

8.提供 TCP/IP、ODBC 和 JDBC等多种数据库连接途径。

9.提供用于管理、检查、优化数据库操作的管理工具。

10.支持大型的数据库。可以处理拥有上千万条记录的大型数据库。

11.支持多种存储引擎。

12.MySQL 是开源的,所以你不需要支付额外的费用。

13.MySQL 使用标准的 SQL数据语言形式。

14.MySQL 对 PHP 有很好的支持,PHP是比较流行的 Web 开发语言。

15.MySQL是可以定制的,采用了 GPL协议,你可以修改源码来开发自己的 MySQL 系统。

16.在线 DDL/更改功能,数据架构支持动态应用程序和开发人员灵活性(5.6新增)

17.复制全局事务标识,可支持自我修复式集群(5.6新增)

18.复制无崩溃从机,可提高可用性(5.6新增)

19.复制多线程从机,可提高性能(5.6新增)

20.3倍更快的性能(5.7 [3] 新增)

21.新的优化器(5.7新增)

22.原生JSON支持(5.7新增)

23.多源复制(5.7新增)

24.GIS的空间扩展 [4] (5.7新增)

优势:

在不同的引擎上有不同 的存储方式。

查询语句是使用传统的sql语句,拥有较为成熟的体系,成熟度很高。

开源数据库的份额在不断增加,mysql的份额页在持续增长。

缺点:

在海量数据处理的时候效率会显著变慢。

4.是否支持事务…

mongo不支持

redis mysql支持

八.Python基础问题:

1.Python2和Python3的区别,如何实现python2代码迁移到Python3环境

区别:字符串类型、默认字符、print方法、除法的数字类型、导入方式、继承类、元类声明、异常处理、字典、模块合并、部分模块重命名(详情)

代码迁移:python3有个内部工具叫做2to3.py,位置在Python3/tool/script文件夹。

首先CD到这个文件夹,然后调用

py 2to3.py -w f:/xxxx/xxx.py

具体方法:https://blog.csdn.net/xutiantian1412/article/details/79523953

2.Python2和Python3的编码方式有什么差别

在python2中主要有str和unicode两种字符串类型,而到python3中改为了bytes和str,并且一个很重要的分别是,在python2中如果字符串是ascii码的话,str和unicode是可以直接进行连接和比较,但是到python3中就不行了,bytes和str是两个独立的类型。另一个重要的是python2中不管是str还是unicode都可以直接写入文件,而不需要加上它是不是str的类型写入方式,但是在python3中如果是写或者读bytes类型就必需带上’b’.

3.迭代器,生成器,装饰器

生成器

创建python迭代器的过程虽然强大,但是很多时候使用不方便。生成器是一个简单的方式来完成迭代。简单来说,Python的生成器是一个返回可以迭代对象的函数。

为什么使用生成器

更容易使用,代码量较小,内存使用更加高效。比如列表是在建立的时候就分配所有的内存空间,而生成器仅仅是需要的时候才使用,更像一个记录代表了一个无限的流。如果我们要读取并使用的内容远远超过内存,但是需要对所有的流中的内容进行处理,那么生成器是一个很好的选择,比如可以让生成器返回当前的处理状态,由于它可以保存状态,那么下一次直接处理即可。

流水线生成器

最简单的生成器:

g = (x*x for x in range(10))for i in g:     print(i)

函数方法实现稍复杂的生成器:

def fib(max):     n,a,b=0,1,1     while n<max:         yield b         a,b=b,a+b         n +=1for n in fib(6):     print(n)

迭代器概述

可以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如list,tuple,dict,set,str等

一类是generator ,包括生成器和带yeild的generator function

这些可以 直接作用于for循环的对象统称为可迭代对象:Iterable

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

def hs(n):     i=0     while i<n:         yield i         i+=1a=(hs(1000))print(next(a))print(next(a))print(next(a))print(next(a))print(next(a))

装饰器

基本概念:在函数调用前后自动打印日志,又不改变原函数,在代码运行期间动态增加功能的方式称之为“装饰器”。

装饰器的语法以@开头,接下来是装饰器函数的名字和可选的参数,紧跟着装饰器声明的是被修饰的函数和装饰函数的可选参数。

**3大特征:**1,外部函数包含内部函数,2,返回一个内部函数,3,内部函数用到外部函数的变量

import timedef hs(f):#装饰器的函数,f接受被装饰的函数名     def neibu():#装饰内部函数         start=time.time()         f()#调用被装饰的函数         end=time.time()         print(end-start)     return neibu#装饰器返回内部函数,(内部代表的是包装盒)@hs#@加函数名,代表下面的函数被hs装饰def jisuan():     print('123456') jisuan()

4.Python的数据类型

Number(数字) 包括int,long,float,complex

String(字符串) 例如:hello,“hello”,hello

List(列表) 例如:[1,2,3],[1,2,3,[1,2,3],4]

Dictionary(字典) 例如:{1:“nihao”,2:“hello”}

Tuple(元组) 例如:(1,2,3,abc)

Bool(布尔) 包括True、False

九.协议问题:

1.robots协议是什么?

Robots协议(也称为爬虫协议、机器人协议等)的全称是“网络爬虫排除标准”(Robots Exclusion Protocol),网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。

1.http协议,请求由什么组成,每个字段分别有什么用,https和http有什么差距

请求行(request line)、请求头部(header)、空行和请求数据四个部分组成(详情);

  • 请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本;
  • 请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息;
  • 空行,请求头部后面的空行是必须的;

④请求数据也叫主体,可以添加任意的其他数据;

 HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

HTTPS和HTTP的区别主要如下:

  • https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
  • http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  • http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

④ http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

2.证书问题

https://blog.csdn.net/fangqun663775/article/details/55189107

3.TCP,UDP各种相关问题

https://blog.csdn.net/xiaobangkuaipao/article/details/76793702

十.数据提取问题:

1.主要使用什么样的结构化数据提取方式,可能会写一两个例子

JSON文件

JSON Path

转化为Python类型进行操作(json类)

XML文件

转化为Python类型(xmltodict)

XPath

CSS选择器

正则表达式

详情:https://www.cnblogs.com/miqi1992/p/7967399.html

2.正则的使用

http://tool.oschina.net/uploads/apidocs/jquery/regexp.html

文本、电话号码、邮箱地址

"^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}KaTeX parse error: Can't use function '\.' in math mode at position 34: …[a-zA-Z0-9_-]+(\̲.̲[a-zA-Z0-9_-]+)…

元字符     含义

.        匹配除换行符以外的任意一个字符

^       匹配行首

$       匹配行尾

?     重复匹配0次或1 *       重复匹配0次或更多次 +     重复匹配1次或更多次

{n,}   重复n次或更多次

{n,m}         重复n~m次

[a-z] 任意字符

[abc]         a/b/c中的任意一个字符

{n}    重复n次

\b     匹配单词的开始和结束

\d     匹配数字

\w    匹配字母,数字,下划线

\s      匹配任意空白,包括空格,制表符(Tab),换行符

\W    匹配任意不是字母,数字,下划线的字符

\S      匹配任意不是空白符的字符

\D     匹配任意非数字的字符

\B     匹配不是单词开始和结束的位置

[^a]  匹配除了a以外的任意字符

[^(123|abc)]     匹配除了123或者abc这几个字符以外的任意字符

3.动态加载的数据如何提取

爬取动态页面目前来说有两种方法

  • 分析请求页面
  • 通过Selenium模拟浏览器获取(不推荐这种,太慢)

分析很简单,我们只需要打开了浏览器F12开发者模式,获取它的js请求文件(除JS选项卡还有可能在XHR选项卡中,当然 也可以通过其它抓包工具)

我们打开第一财经网看看,发现无法获取元素的内容

打开Network,看下它的请求,这里我们只看它的 j s 请求就够了, 找到json接口

将它的url放到浏览器看下,发现是我们要的数据,就可以获取了

一些网站所有的接口都进行了加密操作,我们无法解析js,就必须采用selenium+phantomjs进行获取

4.json数据如何提取

采用正则表达式解析:获取整个json数据后,采用正则表达式匹配到关键词;

使用eval解析

json.loads() 是把Json格式字符串解码转换成Python对象,如果在json.loads的时候出错,要注意被解码的Json字符的编码,json.dumps() 是把json_obj 转换为json_str

十一、其他常见面试

1、Post 和 Get 区别

①GET数据传输安全性低,POST传输数据安全性高,因为参数不会被保存在浏览器历史或web服务器日志中;

②在做数据查询时,建议用GET方式;而在做数据添加、修改或删除时,建议用POST方式;

  • GET在url中传递数据,数据信息放在请求头中;而POST请求信息放在请求体中进行传递数据;

④GET传输数据的数据量较小,只能在请求头中发送数据,而POST传输数据信息比较大,一般不受限制;

⑤在执行效率来说,GET比POST好

2、Python里面如何拷贝一个对象?(赋值,浅拷贝,深拷贝的区别)

赋值:把等号右边的数据,存储到左边变量所开辟的内存空间中

浅拷贝:只拷贝引用不拷贝对象本身,一旦有一个引用修改,所有的引用都会被迫修改

深拷贝:直接拷贝对象本身,产生一个新的对象,并且产生一个新的引用;就是在内存中重新开辟一块空间,不管数据结构多么复杂,只要遇到可能发生改变的数据类型,就重新开辟一块内存空间把内容复制下来,直到最后一层,不再有复杂的数据类型,就保持其原引用。这样,不管数据结构多么的复杂,数据之间的修改都不会相互影响

3、什么是线程和进程?

进程:一个程序在操作系统中被执行以后就会创建一个进程,通过进程分配资源(cpu、内存、I/O设备),一个进程中会包含一到多个线程,其中有一个线程叫做主线程用于管理其他线程。一个正在运行的程序可以看做一个进程,进程拥有独立运行所需要的全部资源。(例如:打开QQ相当于开启一个进程)

线程:程序中独立运行的代码段。在一个进程执行的过程,一般会分成很多个小的执行单位,线程就是这些执行单位;在处理机调度,以线程为单位件进行,多个线程之间并发执行,线程占用的是cpu。

一个程序至少拥有一个进程,一个进程至少拥有一个线程。进程负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。

多线程

多线程定义:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

多线程优点:可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。

所以为了提高爬虫的效率,我们可以使用多线程来下载文件,在python中我们使用threading模块来创建多线程,从而实现多线程爬虫。(补充:Python3不能再使用thread模块)。

多线程使用的场合:耗时操作(访问外存,即:I/O,访问网络资源),为了不阻碍主线程或者其他的操作,一般会采用多线程。

4、爬虫使用多线程好?还是多进程好?为什么?

对于IO密集型代码(文件处理,网络爬虫),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,会造成不必要的时间等待,而开启多线程后,A线程等待时,会自动切换到线程B,可以不浪费CPU的资源,从而提升程序执行效率)。

在实际的采集过程中,既考虑网速和相应的问题,也需要考虑自身机器硬件的情况,来设置多进程或者多线程。

5、什么是协程?

协程是:在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。他本身是一种特殊的子程序或者称作函数。

遇到IO密集型的业务时,多线程加上协程,你磁盘在那该读读该写写,我还能去干点别的。在WEB应用中效果尤为明显。

协程的好处:

跨平台

跨体系架构

无需线程上下文切换的开销

无需原子操作锁定及同步的开销

方便切换控制流,简化编程模型

高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

6、什么是并行和并发?

并行:多个进程在同一时刻同时进行

并发:多个进程在同一时间段内交替进行 (操作系统大多采用并发机制),根据一定的算法(常见的就是时间片轮询算法)

7,__new)_和__init__的区别

new:它是创建对象时调用,会返回当前对象的一个实例,可以用_new_来实现单例

init:它是创建对象后调用,对当前对象的一些实例初始化,无返回值

8,列举爬虫用到的网络数据包,解析包?

网络数据包 urlliburllib2requests

解析包 rexpathbeautiful souplxml

9.如何显著提升爬虫的效率

使用性能更好的机器

使用光纤网络

多进程

多线程

分布式

10.如何提升scrapy的爬取效率

(1)增加并发

默认scrapy开启的并发线程为32个, 可以适当进行增加. 在settings配置文件中修改`CONCURRENT_REQUESTS = 100值为100, 并发设置成了为100.

(2)降低日志级别

在运行scrapy时, 会有大量日志信息的输出, 为了减少CPU的使用率. 可以设置log输出信息为INFO或者ERROR. 在配置文件中编写: LOG_LEVEL = ‘INFO’

(3)禁止cookie

如果不是真的需要cookie, 则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率, 提升爬取效率. 在配置文件中编写: COOKIES_ENABLED = False.

(4)禁止重试

对失败的HTTP进行重新请求(重试)会减慢爬取速度, 因此可以禁止重试. 在配置文件中编写: RETRY_ENABLED = False

(5)减少下载超时

如果对一个非常慢的链接进行爬取, 减少下载超时可以能让卡住的链接快速被放弃, 从而提升效率. 在配置文件中进行编写: DOWNLOAD_TIMEOUT = 10 超时时间为10s.

11,使用redis搭建分布式系统时如何处理网络延迟和网络异常?

由于网络异常的存在,分布式系统中请求结果存在“三态”的概念,即三种状态:“成功”、“失败”、“超时(未知)”

当出现“超时”时可以通过发起读取数据的操作以验证 RPC 是否成功(例如银行系统的做法)

另一种简单的做法是,设计分布式协议时将执行步骤设计为可重试的,即具有所谓的“幂等性”

12.你是否了解mysql数据库的几种引擎?

InnoDB:

InnoDB是一个健壮的事务型存储引擎,这种存储引擎已经被很多互联网公司使用,为用户操作非常大的数据存储提供了一个强大的解决方案。

在以下场合下,使用InnoDB是最理想的选择:

1.更新密集的表。InnoDB存储引擎特别适合处理多重并发的更新请求。

2.事务。InnoDB存储引擎是支持事务的标准MySQL存储引擎。

3.自动灾难恢复。与其它存储引擎不同,InnoDB表能够自动从灾难中恢复。

4.外键约束。MySQL支持外键的存储引擎只有InnoDB。

5.支持自动增加列AUTO_INCREMENT属性。

一般来说,如果需要事务支持,并且有较高的并发读取频率,InnoDB是不错的选择。

MEMORY:

使用MySQL Memory存储引擎的出发点是速度。为得到最快的响应时间,采用的逻辑存储介质是系统内存。

虽然在内存中存储表数据确实会提供很高的性能,但当mysqld守护进程崩溃时,所有的Memory数据都会丢失。

获得速度的同时也带来了一些缺陷。

一般在以下几种情况下使用Memory存储引擎:

1.目标数据较小,而且被非常频繁地访问。在内存中存放数据,所以会造成内存的使用,可以通过参数max_heap_table_size控制Memory表的大小,设置此参数,就可以限制Memory表的最大大小。

2.如果数据是临时的,而且要求必须立即可用,那么就可以存放在内存表中。

3.存储在Memory表中的数据如果突然丢失,不会对应用服务产生实质的负面影响。

十二、 Tornado

什么是RESTful

全称:Representational State Transfer

是HTTP协议(1.0和1.1)的主要设计者Roy Thomas Fielding提出

资源(Resources) 表现层(Representational )状态转化(State Transfer)

是实现API的一种风格

RESTful风格

Resources(资源):使用URL指向一个实体,例如:网页是资源,媒体也是资源

Representational (表现层):资源的表现形式,例如:图片,HTML文本等

State Transfer(状态转化):GET, POST, PUT, DELETE HTTP动词操作资源

例如后端的增删改查,增删改查可以和http请求联系起来

常用HTTP动词

RESTful解释:

GET, POST, PUT, DELETE 分别用来 获取,新建,更新,删除资源

幂等性:GET, PUT, DELETE

幂等性是指无论一次还是多次操作都具有一样的副作用

POST不具有幂等性,因为post每次都创建一个新的,

对于幂等性操作可以我们可以放心的发多次操作

对于非幂等性我,我们需要在后台保证发送多次不会创建多次

Tornado RESTful Api示例

MVC框架:

M:model表示操作数据库层

V:view表示视图层

C:controller 表示业务逻辑层

我们省略视图层,来实现微服务,进行用户管理(增删改查)

十三、python基础代码

画三角形星号

cs = int(input('请输入层数:'))for i in range(cs):     j = 1     while j<=2*i+1:         print('*',end='')         j+=1     print()

求一个数的倒过来得数

i = int(input('请输入一个整数:')) j = 0while i>0:     j = j*10+i%10     i//=10print(j)

求一个元素在列表里出现了几次

a = [1, 1, 5, 2, 5, 2, 5, 2, 6,] b = set(a)for i in b:     print(i,a.count(i))

查找变量的顺序

- LEGB法则

L-----LOCAL 局部

E-------ENCLOSE------嵌套作用域

G-------GLOBLE-------全局

B---------BUILT-IN------内置

a = 100b = 2c = 10def waibu():     b=200     c=2     def neibu():         c=300         print(c)#LOCAL局部变量         print(b)#ENCLOSE嵌套         print(a)#GLOBAL全局         print(max)#BUILT-IN内置     neibu() waibu()

递归阶乘

def jiecheng(n):     if  n==1:         return 1     else:         return n*jiecheng(n-1) s=jiecheng(5)print(s)

a=[lambda x:x*i for i in range(3)]print(a[0](3)) # 6print(a[1](3)) # 6print(a[2](3)) # 6

class A():     def __init__(self):         print('A开始')         print('A结束')class B(A):     def __init__(self):         print('B开始')         super().__init__()         print('B结束')class C(A):     def __init__(self):         print('C开始')         super().__init__()         print('C结束')class D(B, C):     def __init__(self):         print('D开始')         super().__init__()         print('D结束') d = D()

输出结果为:

D开始

B开始

C开始

A开始

A结束

C结束

B结束

D结束

单例

class   Car():     def __new__(cls, *args, **kwargs):         if not hasattr(Car,'inst'):#如果Car里面没有inst属性             Car.inst=object.__new__(Car)#就建立一个Car对象,给inst属性         return Car.inst#返回一个属性inst     def __init__(self,name,cid):         print('你好')         self.name=name         self.cid=cid a=Car('宝马','京A66666') b=Car('奔驰','京B66666')print(a.name,a.cid)print(b.name,b.cid)输出: 你好 你好 奔驰 京B66666 奔驰 京B66666

多态

class   A():     def j(self):         print('aaa')class   B():     def j(self):         print('bbb')class   C():     def j(self):         print('ccc')def e(d):     d.j() f=A() g=B() k=C() e(f) e(g) e(k)

观察者模式

class   Resi():     def __init__(self):         self.obsv=[]         self.stautus=''     def attach(self,ob):         self.obsv.append(ob)     def notify(self):         for x in self.obsv:             x.update()class   Observe():     def __init__(self,name,rs):         self.name=name         self.rs=rs     def update(self):         print('%s %s 请不要打游戏了'%(self.rs.status,self.name))class   Manager():     def __init__(self,name,boss):         self.name=name         self.boss=boss     def update(self):         print('%s %s 请到北京饭店开会'%(self.boss.status,self.name))if  __name__=='__main__':     resi=Resi()     obj_zs=Observe('张三',resi)     obj_ls=Observe('李四',resi)     obj_xh=Observe('小红',resi)     m_lqd=Manager('刘强东',resi)     m_my=Manager('马云',resi)     resi.attach(obj_zs)     resi.attach(obj_ls)     resi.attach(obj_xh)     resi.attach(m_lqd)     resi.attach(m_my)     resi.status='老板来啦'resi.notify()

策略模式

class   CashNormal():     def accept_money(self,money):         return moneyclass   CashRate():     def __init__(self,rate):         self.rate=rate     def accept_money(self,money):         return money*self.rateclass   CashReturn():     def __init__(self,conditon,ret):         self.conditon=conditon         self.ret=ret     def accept_money(self,money):         return money-(money//self.conditon)*self.retclass   Context():     def __init__(self,cs):         self.cs=cs     def get_result(self,money):        return self.cs.accept_money(money)if __name__ == '__main__':     zd={}     zd[1]=Context(CashNormal())     zd[2]=Context(CashRate(0.8))     zd[3]=Context(CashReturn(300,50))     celue=int(input('请输入策略'))     if celue in zd.keys():         cs=zd[celue]     else:         cs=zd[1]     money=float(input('请输入金额'))print(cs.get_result(money))

工厂模式

class   Bmw():     def __init__(self,name):         self.name=nameclass   Benz():     def __init__(self,name):         self.name=nameclass   Carfactory:     @staticmethod     def makecar(name):         if name=='宝马':             return Bmw(name)         elif    name=='奔驰':             return Benz(name) car=Carfactory.makecar('宝马')print(car.name,type(car)) car1=Carfactory.makecar('奔驰')print(car1.name,type(car1))

析构函数

class   A():     count=0     def __init__(self,name):         self.name=name         A.count+=1         print('加上',self.name,'还有%d个对象'%A.count)     def __del__(self):         A.count-=1         print('删除',self.name,'还剩%d个对象'%A.count) a=A('张三') b=A('李四')del adel b

二分查找

# 重要前提:二分查找的前提是有序

# 例如:【2,5,1,4,1,3】

# 先排序:【1,1,2,3,4,5】

# 先拿出列表中间的那个元素,和num进行比较,如果num大于中间的那个元素,

# 则表明:应该下次在中间和末尾之间的元素查找

# 如果num小于中间的这个元素,则表明:num在开始的元素和中间元素之间

list_1=[2,5,1,4,1,3] list_1.sort()print("排序之后的列表:",list_1) num=int(input("请输入你要查找的数字:")) first=0last=len(list_1)-1while   first<last:     mid=(first+last)//2     if list_1[mid]==num:         print("找到了")         break     elif    list_1[mid]>num:         last=mid-1     elif    list_1[mid]<num:         first=mid+1else:     print("没找到") list_1.sort()print("拍完序之后的列表为:",list_1) num=int(input("请输入你要查找的数字:")) first=0last=len(list_1)-1while   first<last:     mid=(first+last)//2     if  list_1[mid]==num:         print("找到啦")         break     elif    list_1[mid]<num:         first=mid+1     elif    list_1[mid]>num:         last=mid-1else:     print("谁知道在哪")

冒泡排序

冒泡排序算法的运作如下:

比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。

对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

针对所有的元素重复以上的步骤,除了最后一个。

持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

list_1=[2,3,1,5,6,4] gs=len(list_1)for i in range(0,gs-1):     for j in range(0,gs-i-1):         if list_1[j]>list_1[j+1]:             list_1[j],list_1[j+1]=list_1[j+1],list_1[j]print(list_1)

空心塔型

cs = int(input("请输入要打印的层数:"))for i in range(cs):     for j in range(cs-i-1):         print(' ',end='')     for k in range(2*i+1):         if k==0 or k==2*i or i==cs-1:#条件控制输出的空心塔型             print('*',end='')         else:             print(' ',end='')     i+=1     print()

    请输出要打印的层数7

      *

     * *

    *   *

   *     *

  *       *

*         *

*************

塔型

cs = int(input("请输入需要打印的层数:"))for i in range(cs):     for j in range(cs-i-1):         print(' ',end='')     for k in range(2*i+1):#这里的范围取值决定了塔型每行的字符数         print('*',end='')     i+=1     print()

输出:

       *

      ***

     *****

    *******

   *********

  ***********

*************

***************

十四、笔试题

1、python 常用数据结构有哪些?请简要介绍一下。

①列表:list=[val1,val2,val3,val4] ②元组:tuple=(val1,val2,val3,val4) ③字典:dict={key1:val1,key2:val2} ④集合:set={val1,val2,val3,val4}

简要描述 Python 中单引号、双引号、三引号的区别。

①单引号中可以使用双引号,中间的会当做字符串输出

②双引号中可以使用单引号,中间的会当做字符串输出

  • 三单引号和三双引号中间的字符串在输出时保持原来的格式

2、如何在一个 function 里面设置一个全局的变量?

Global VarName

3、Python 里面如何拷贝一个对象?(赋值、浅拷贝、深拷贝的区别)

1.赋值: 只是复制了新对象的引用,不会开辟新的内存空间。 2.浅拷贝: 创建新对象,其内容是原对象的引用。     浅拷贝有三种形式:切片操作,工厂函数,copy模块中的copy函数。     如: lst = [1,2,3,[4,5]]     切片操作:lst1 = lst[:] 或者 lst1 = [each for each in lst]     工厂函数:lst1 = list(lst)     copy函数:lst1 = copy.copy(lst)

浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,在lst中有一个嵌套的list[4,5],如果我们修改了它,情况就不一样了。

3.深拷贝:只有一种形式,copy模块中的deepcopy函数。      和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。      深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。

如果 custname 字符串的内容为 utf-8 的字符,如何将 custname 的内容转为 gb18030 的字符串?

custname = custname.decode(‘utf-8’).encode(‘b18030’)

4、请写出一段 Python 代码实现删除一个 list 里面的重复元素。

②利用集合set特性,元素是非重复的

a = [1,2,3,4,5,1,2,3]def fun1(a):     a=list(set(a))     print(a) fun1(a)

①利用字典的from keys来自动过滤重复值

a = [1, 2, 3, 4, 5, 1, 2, 3]def fun1(a):     b = {}     b = b.fromkeys(a)     c = list(b.keys())     print(c) fun1(a)

5、这两个参数是什么意思:args,*kwargs?

首先,我想说的是*args和**kwargs并不是必须这样写,只有前面的*和**才是必须的。你可以写成*var和**vars。而写成*args和**kwargs只是约定俗称的一个命名规定。

*args和*kwargs主要用于函数定义,你可以将不定量的参数传递给一个参数。其中*args是用来发送一个非键值对的可变数量的参数列表给一个函数;**kwargs 允许你将不定长度的键值对, 作为参数传递给一个函数。 如果你想要在一个函数里处理带名字的参数, 你应该使用**kwargs

这里传递了3个参数,按位置传参,'123'为test传参,'hello'和'2019'为*args传参,这里传了2个参数。

def import_args(test, *args):     print('param1', test)     for item in args:         print('other param', item) import_args('123', 'hello', '2019')

注意,看下面的*args的另一种用法:用来解压数据。

def import_args(test, *args):

print('param1', test)   

for item in args:       

    print('other param', item)

args = ['hello', '2019']

import_args('123', *args)

输出结果:

1 param1 123

2 other param hello

3 other param 2019

这段代码和上面的效果是一样的,但是这里第8行的*args和第1行的*args可是不一样的。第一行是表示函数可以接受不定数量的非键值对的参数,用来传参使用的。第八行是用来解压列表

['hello', '2019']的每一项数据的,用来解压参数的。这是*args的两种用法,也可说是*的两种用法,因为args是可变的。

接下来说说**kwargs。

def import_kwargs(test, **kwargs):

     print('param1', test)

     for key, value in kwargs.items():

         print(key, value)

d = {'name': 'jack', 'age': 26}

import_kwargs('123', **d)

**kwargs用来传递带键值对的参数,而**也是用来解压字典容器内的参数。

输出结果:

param1 123

name jack

age 26

总结:*args**kwargs都是用于函数中传递参数的*args传递的是非键值对的参数,**kwargs传递的是带键值对的参数,如果还有普通参数需要传递,那么应该先传递普通的参数。

6、统计如下 list 单词及其出现次数。

a=['apple', 'banana', 'apple', 'tomato', 'orange', 'apple', 'banana', 'watermeton']

方法一:

利用字典

1 a = ['apple', 'banana', 'apple', 'tomato', 'orange', 'apple', 'banana', 'watermeton']2 dic = {}3 for key in a:4     dic[key] = dic.get(key, 0) + 15 print(dic)

输出结果:

1 {'apple': 3, 'banana': 2, 'tomato': 1, 'orange': 1, 'watermeton': 1}

方法二:

利用python的collections包。

1 from collections import Counter2 3 a = ['apple', 'banana', 'apple', 'tomato', 'orange', 'apple', 'banana', 'watermeton']4 d = Counter(a)5 print(d)

输出结果:

1 Counter({'apple': 3, 'banana': 2, 'tomato': 1, 'orange': 1, 'watermeton': 1})  # 是一个类似字典的结构

7、给列表中的字典排序:假设有如下 list 对象

alist=[{"name":"a", "age":20}, {"name":"b", "age":30}, {"name":"c", "age":25}],将 alist 中的元素按照 age 从大到小排序。

利用list的内建函数,list.sort()来进行排序。

1 alist = [{"name": "a", "age": 20}, {"name": "b", "age": 30}, {"name": "c", "age": 25}]2 alist.sort(key=lambda x: x['age'])3 print(alist)

这是一种效率很高的排序方法。

输出结果:

1 [{'name': 'a', 'age': 20}, {'name': 'c', 'age': 25}, {'name': 'b', 'age': 30}]

8、写出下列代码的运行结果

1 a = 12 def fun(a):3     a = 24 fun(a)5 print(a)1

分析,在函数外面定义了一个全局变量a为1,在函数内部定义了一个局部变量a为2。局部变量在离开函数后就失效了。

所以,结果为全局变量的a的值。如果在a=2之前加上global a,声明为全局变量,那么结果为2。

1 a = []2 def fun(a):3     a.append(1)4 fun(a)5 print(a)[1]这是因为,将a传入到function中,这相当于对a进行赋值引用。由于a是可变类型的,所以在函数内部修改a的时候,外部的全局变量a也跟着变化。1 class Person:2     name = 'Lily'4 p1 = Person()5 p2 = Person()6 p1.name = 'Bob'7 print(p1.name)8 print(p2.name)9 print(Person.name)

Bob,Lily,Lily

9、假设有如下两个 list:a = ['a', 'b', 'c', 'd', 'e'],b = [1, 2, 3, 4, 5],将 a 中的元素作为 key,b 中元素作为 value,将 a,b 合并为字典。

a = ['a', 'b', 'c', 'd', 'e'] b = [1, 2, 3, 4, 5] dic=dict(zip(a,b)) print(dic)

使用 python 已有的数据结构,简单的实现一个栈结构。

class Stack(object):     def __init__(self,size):         self.size=size         self.stack = []         self.top = 0     # 初始化,top=0是则为空栈     def push(self,x):         if self.stackFull():# 进栈之前检查栈是否已满             raise Exception("overflow")         else:             self.stack.append(x)             self.top=self.top+1 #push进去的第一个元素为下标为1     def pop(self):         if self.stackEmpty():             raise Exception('underflow!')         else:             self.top=self.top-1             return self.stack.pop()#利用Python内建函数pop()实现弹出     def stackEmpty(self):         if self.top ==0:             return True         else:             return False     def stackFull(self):         if self.top == self.size:             return True         else:             return False if __name__=='__main__':     s=Stack(10)     for i in range(9):         s.push(i)         s.pop()     print(s.top)     print(s.size)     print(s.stack)

十五、面试官提的问题

1、项目上遇到了哪些难点?如何解决?

验证码

2、数据如何去重,清洗,存入数据库?

去重:https://www.cnblogs.com/518894-lu/p/9119559.html

清洗:https://blog.csdn.net/a13526863959/article/details/84587143,

https://blog.csdn.net/wanght89/article/details/78188591

3、有一个升序后又降序的数组,比如 1356742,如何查找到 2 的位置。

# _*_ coding:UTF-8 _*_ from click._compat import raw_input def find(list,a):     for i in range(1,len(list)):         if list[i]==a:             print(i)     else:         return None find(raw_input('请输入列表:'),raw_input('请输入要查找的元素:'))

解决方法2: 使用python的内建函数enumerate

enumerate() 解析:同时枚举出序列的下标和值,这个是一个非常有用的函数,可以避免很多丑陋的代码

# _*_ coding:UTF-8 _*_ from click._compat import raw_input def find(list,a):     for i,item in enumerate(list):         if item==a:             print(i)     else:         return None find(raw_input('请输入列表:'),raw_input('请输入要查找的元素:'))

十六、110道python面试笔试题汇总

1、一行代码实现1--100之和

利用sum()函数求和

print(sum(range(0,101)))

5050

2、如何在一个函数内部修改全局变量

利用global 修改全局变量

a = 5 def fn():     global a     a=4 fn() print(a)4

3、列出5个python标准库

os:提供了不少与操作系统相关联的函数

sys:   通常用于命令行参数

re:   正则匹配

math: 数学运算

datetime:处理日期时间

4、字典如何删除键和合并两个字典

del和update方法

dic = {'height':'28','age':'12'}del dic['age']print(dic) {'height': '28'} dic.update({'name':'22'})print(dic) {'height': '28', 'name': '22'}

5、谈下python的GIL

GIL 是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。

多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大

6、python实现列表去重的方法

先通过集合去重,再转列表

list = [1,5,8,8,2,1,1,5,2,1,1,1] a= set(list)print(a) {8, 1, 2, 5} b = [x for x in a]print(b) [8, 1, 2, 5]

7、fun(*args,**kwargs)中的*args,**kwargs什么意思?

首先,我想说的是*args和**kwargs并不是必须这样写,只有前面的*和**才是必须的。你可以写成*var和**vars。而写成*args和**kwargs只是约定俗称的一个命名规定。

*args和*kwargs主要用于函数定义,你可以将不定量的参数传递给一个参数。这里的不定的意思是:预先并不知道函数使用者会传递多少个参数给你,所以在这个场景下使用者两个关键字。其中*args是用来发送一个非键值对的可变数量的参数列表给一个函数;**kwargs 允许你将不定长度的键值对, 作为参数传递给一个函数。 如果你想要在一个函数里处理带名字的参数, 你应该使用**kwargs

8、python2和python3的range(100)的区别

python2返回列表,python3返回迭代器,节约内存

9、一句话解释什么样的语言能够用装饰器?

函数可以作为参数传递的语言,可以使用装饰器

10、python内建数据类型有哪些

整型--int

布尔型--bool

字符串--str

列表--list

元组--tuple

字典—dict

11、简述面向对象中__new__和__init__区别

__init__是初始化方法,创建对象后,就立刻被默认调用了,可接收参数,如下

class Bike:     def __init__(self,newWheelNum,NewColor):         self.wheelNum = newWheelNum         self.color = NewColor     #__init__方法自动被调回,可以创建对象接受参数     def move(self):         print('车会跑') #创建对象 BM = Bike(2,'green') print('车的颜色为:%s' %BM.color) print('车的轮子数量为:%d' %BM.wheelNum) 车的颜色为:green 车的轮子数量为:2 #只打印__init__方法执行的结果,move方法未执行

1、__new__至少要有一个参数cls,代表当前类,此参数在实例化时由Python解释器自动识别

2、__new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类(通过super(当前类名, cls))__new__出来的实例,或者直接是object的__new__出来的实例

3、__init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值

4、如果__new__创建的是当前类的实例,会自动调用__init__函数,通过return语句里面调用的__new__函数的第一个参数是cls来保证是当前类实例,如果是其他类的类名,;那么实际创建返回的就是其他类的实例,其实就不会调用当前类的__init__函数,也不会调用其他类的__init__函数。

class A(object):     def __init__(self):         print('这是init方法',self)     def __new__(cls):         print('这是cls的ID:',id(cls))         print('这是new方法',object.__new__(cls))         return object.__new__(cls) A()print('这是类A的ID:',id(A))这是cls的ID: 47677960  #cls和类ID一样,说明指向同一个类,也就是cla就是创建的实例类这是new方法 <__main__.A object at 0x00307350>#init方法中的self和new方法返回值地址一样这是init方法 <__main__.A object at 0x00307350>#说明返回值是对象这是类A的ID: 47677960#cls和类ID一样,说明指向同一个类,也就是cla就是创建的实例类

12、简述with方法打开处理文件帮我我们做了什么?

打开文件在进行读写的时候可能会出现一些异常状况,如果按照常规的f.open写法,我们需要try,except,finally,做异常判断,并且文件最终不管遇到什么情况,都要执行finally f.close()关闭文件

f = open("./1.txt",'wb')try:     f.writ("hello world")except:     pass finally:     f.close()

with方法帮我们实现了finally中f.close(当然还有其他自定义功能,有兴趣可以研究with方法源码)

with open('/path/to/file', 'r') as f:     print(f.read())

13、列表[1,2,3,4,5],请使用map()函数输出[1,4,9,16,25],并使用列表推导式提取出大于10的数,最终输出[16,25]

map()函数第一个参数是fun,第二个参数是一般是list,第三个参数可以写list,也可以不写,根据需求

list = [1,2,3,4,5]def fn(x):     return x**2res = map(fn,list) res = [i for i in res if i >10]print(res) [16, 25]

14、python中生成随机整数、随机小数、0--1之间小数方法

随机整数:random.randint(a,b),生成区间内的整数

随机小数:习惯用numpy库,利用np.random.randn(5)生成5个随机小数

0-1随机小数:random.random(),括号中不传参

import random import numpy as np result = random.randint(10,20)#区间 res = np.random.randn(5) ret = random.random()#不能传数值 print('正整数:',result) print('5个随机小数:',res) print('0-1随机小数;',ret) 正整数: 14 5个随机小数: [-0.67833428  1.53692927  0.33841477 -1.24463382 -0.27630526] 0-1随机小数; 0.2925398128464497

15、避免转义给字符串加哪个字母表示原始字符串?

r , 表示需要原始字符串,不转义特殊字符

16、<div class="nam">中国</div>,用正则匹配出标签里面的内容(“中国”),其中class的类名是不确定的

import re str = '<div class="nam">中国</div>'re = re.findall(r'<div class=".*">(.*?)</div>',str)#".*"中 .代表可有可无,*代表任意字符,满足类名可以变化,(.*?)提取文本print(re) ['中国']

17、python中断言方法举例

assert()方法,断言成功,则程序继续执行,断言失败,则程序报错

a=3assert (a>1)print("断言成功,程序继续向下执行") b=4assert (b>7)print("断言失败,程序报错")

断言成功,程序继续向下执行

Traceback (most recent call last):

  File "F:/items/生成器.py", line 6, in <module>

    assert (b>7)

AssertionError

18、数据表student有id,name,score,city字段,其中name中的名字可有重复,需要消除重复行,请写sql语句

select  distinct  name  from  student

19、10个Linux常用命令

ls  pwd  cd  touch  rm  mkdir  tree  cp  mv  cat  more  grep  echo

20、python2和python3区别?列举5个

1、Python3 使用 print 必须要以小括号包裹打印内容,比如 print('hi')

Python2 既可以使用带小括号的方式,也可以使用一个空格来分隔打印内容,比如 print 'hi'

2、python2 range(1,10)返回列表,python3中返回迭代器,节约内存

3、python2中使用ascii编码,python3中使用utf-8编码

4、python2中unicode表示字符串序列,str表示字节序列,

python3中str表示字符串序列,byte表示字节序列

5、python2中为正常显示中文,引入coding声明,python3中不需要

6、python2中是raw_input()函数,python3中是input()函数

21、列出python中可变数据类型和不可变数据类型,并简述原理

不可变数据类型:数值型、字符串型string和元组tuple

不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象(一个地址),如下图用id()方法可以打印对象的id

a = 3b = 4print(id(a))print(id(b))2012993360 2012993376

可变数据类型:列表list和字典dict;

允许变量的值发生变化,即如果对变量进行append、+=等这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会变化,不过对于相同的值的不同对象,在内存中则会存在不同的对象,即每个对象都有自己的地址,相当于内存中对于同值的对象保存了多份,这里不存在引用计数,是实实在在的对象。

a = [3] b = {'value':'4'}print(id(a))print(id(b)) a.append(4) b.update({'key':'3'})print(a,id(a))print(b,id(b))59785944 58015056[3, 4] 59785944{'value': '4', 'key': '3'} 58015056

22、s = "ajldjlajfdljfddd",去重并从小到大排序输出"adfjl"

set去重,去重转成list,利用sort方法排序,reverse=False是从小到大排

s = "ajldjlajfdljfddd"s1 = set(s)print(s1) s=list(s1)print(s) s.sort(reverse=False)print(s) res = "".join(s)print(res) {'d', 'f', 'j', 'a', 'l'} ['d', 'f', 'j', 'a', 'l'] ['a', 'd', 'f', 'j', 'l']

adfjl

23、用lambda函数实现两个数相乘

sum = lambda a,b:a*b #表达式print(sum(5,4))#参数20

24、字典根据键从小到大排序

dict={"name":"zs","age":18,"city":"深圳","tel":"1362626627"}

dict={"name":"zs","age":18,"city":"深圳","tel":"1362626627"} list = sorted(dict.items(),key=lambda i:i[0],reverse=False)#dict.items()结果是字典的键值对元组 #i[0]表示键,i[1]表示值,通过lambda函数根据键排序print("sorted根据字典键排序:",list) new_dict={}for i in list:     new_dict[i[0]]=i[1]print("新字典:",new_dict) sorted根据字典键排序: [('age', 18), ('city', '深圳'), ('name', 'zs'), ('tel', '1362626627')]新字典: {'age': 18, 'city': '深圳', 'name': 'zs', 'tel': '1362626627'}

25、利用collections库的Counter方法统计字符串每个单词出现的次数"kjalfj;ldsjafl;hdsllfdhg;lahfbl;hl;ahlf;h"

from collections import Counter a = "kjalfj;ldsjafl;hdsllfdhg;lahfbl;hl;ahlf;h"res = Counter(a)print(res) Counter({'l': 9, ';': 6, 'h': 6, 'f': 5, 'a': 4, 'j': 3, 'd': 3, 's': 2, 'k': 1, 'g': 1, 'b': 1})

26、字符串a = "not 404 found 张三 99 深圳",每个词中间是空格,用正则过滤掉英文和数字,最终输出"张三  深圳"

import re a = 'not 404 found 张三 99 深圳'list = a.split(" ")print(list) res = re.findall('\d+|[a-zA-Z]+',a)#连接多个匹配方式,先匹配数字,再匹配单词print(res)for i in res:     if i in list:         list.remove(i) new_str = " ".join(list)print(new_str) ['not', '404', 'found', '张三', '99', '深圳'] ['not', '404', 'found', '99']张三 深圳

顺便贴上匹配小数的代码,虽然能匹配,但是健壮性有待进一步确认

import re a = 'not 404 50.56 found 张三 99 深圳'list = a.split(" ")print(list) res = re.findall('\d+\.?\d*|[a-zA-Z]+',a)#连接多个匹配方式,先匹配数字,再匹配小数,再匹配单词print(res)for i in res:     if i in list:         list.remove(i) new_str = " ".join(list)print(new_str) ['not', '404', '50.56', 'found', '张三', '99', '深圳'] ['not', '404', '50.56', 'found', '99']张三 深圳

27、filter方法求出列表所有奇数并构造新列表,a =  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表

28、列表推导式求列表所有奇数并构造新列表,a =  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

29、正则re.complie作用

re.compile是将正则表达式编译成一个对象,加快速度,并重复使用

30、a=(1,)b=(1),c=("1") 分别是什么类型的数据?

31、两个列表[1,5,7,9]和[2,2,6,8]合并为[1,2,2,3,6,7,8,9]

extend可以将另一个集合中的元素逐一添加到列表中,区别于append整体添加

32、用python删除文件和用linux命令删除文件方法

python:os.remove(文件名)

linux:       rm  文件名

33、log日志中,我们需要用时间戳记录error,warning等的发生时间,请用datetime模块打印当前时间戳 “2018-04-01 11:38:54”

顺便把星期的代码也贴上了

34、数据库优化查询方法

外键、索引、联合查询、选择特定字段等等

35、请列出你会的任意一种统计图(条形图、折线图等)绘制的开源库,第三方也行

pychart、matplotlib

36、写一段自定义异常代码

自定义异常用raise抛出异常

37、正则表达式匹配中,(.*)和(.*?)匹配区别?

(.*)是贪婪匹配,会把满足正则的尽可能多的往后匹配

(.*?)是非贪婪匹配,会把满足正则的尽可能少匹配

38、简述Django的orm

ORM,全拼Object-Relation Mapping,意为对象-关系映射

实现了数据模型与数据库的解耦,通过简单的配置就可以轻松更换数据库,而不需要修改代码只需要面向对象编程,orm操作本质上会根据对接的数据库引擎,翻译成对应的sql语句,所有使用Django开发的项目无需关心程序底层使用的是MySQL、Oracle、sqlite....,如果数据库迁移,只需要更换Django的数据库引擎即可

39、[[1,2],[3,4],[5,6]]一行代码展开该列表,得出[1,2,3,4,5,6]

列表推导式的骚操作

运行过程:for i in a ,每个i是【1,2】,【3,4】,【5,6】,for j in i,每个j就是1,2,3,4,5,6,合并后就是结果

还有更骚的方法,将列表转成numpy矩阵,通过numpy的flatten()方法,代码永远是只有更骚,没有最骚

40、x="abc",y="def",z=["d","e","f"],分别求出x.join(y)和x.join(z)返回的结果

join()括号里面的是可迭代对象,x插入可迭代对象中间,形成字符串,结果一致,有没有突然感觉字符串的常见操作都不会玩了

顺便建议大家学下os.path.join()方法,拼接路径经常用到,也用到了join,和字符串操作中的join有什么区别,该问题大家可以查阅相关文档,后期会有答案

41、举例说明异常模块中try except else finally的相关意义

try..except..else没有捕获到异常,执行else语句

try..except..finally不管是否捕获到异常,都执行finally语句

42、python中交换两个数值

43、举例说明zip()函数用法

zip()函数在运算时,会以一个或多个序列(可迭代对象)做为参数,返回一个元组的列表。同时将这些序列中并排的元素配对。

zip()参数可以接受任何类型的序列,同时也可以有两个以上的参数;当传入参数的长度不同时,zip能自动以最短序列长度为准进行截取,获得元组。

44、a="张明 98分",用re.sub,将98替换为100

45、写5条常用sql语句

show databases;

show tables;

desc 表名;

select * from 表名;

delete from 表名 where id=5;

update students set gender=0,hometown="北京" where id=5

46、a="hello"和b="你好"编码成bytes类型

47、[1,2,3]+[4,5,6]的结果是多少?

两个列表相加,等价于extend

48、提高python运行效率的方法

1、使用生成器,因为可以节约大量内存

2、循环代码优化,避免过多重复代码的执行

3、核心模块用Cython  PyPy等,提高效率

4、多进程、多线程、协程

5、多个if elif条件判断,可以把最有可能先发生的条件放到前面写,这样可以减少程序判断的次数,提高效率

49、简述mysql和redis区别

redis: 内存型非关系数据库,数据保存在内存中,速度快

mysql:关系型数据库,数据保存在磁盘中,检索的话,会有一定的Io操作,访问速度相对慢

50、遇到bug如何处理

1、细节上的错误,通过print()打印,能执行到print()说明一般上面的代码没有问题,分段检测程序是否有问题,如果是js的话可以alert或console.log

2、如果涉及一些第三方框架,会去查官方文档或者一些技术博客。

3、对于bug的管理与归类总结,一般测试将测试出的bug用teambin等bug管理工具进行记录,然后我们会一条一条进行修改,修改的过程也是理解业务逻辑和提高自己编程逻辑缜密性的方法,我也都会收藏做一些笔记记录。

4、导包问题、城市定位多音字造成的显示错误问题

51、正则匹配,匹配日期2018-03-20

url='https://sycm.taobao.com/bda/tradinganaly/overview/get_summary.json?dateRange=2018-03-20%7C2018-03-20&dateType=recent1&device=1&token=ff25b109b&_=1521595613462'

仍有同学问正则,其实匹配并不难,提取一段特征语句,用(.*?)匹配即可

52、list=[2,3,5,4,9,6],从小到大排序,不许用sort,输出[2,3,4,5,6,9]

利用min()方法求出最小值,原列表删除最小值,新列表加入最小值,递归调用获取最小值的函数,反复操作

53、写一个单列模式

因为创建对象时__new__方法执行,并且必须return 返回实例化出来的对象所cls.__instance是否存在,不存在的话就创建对象,存在的话就返回该对象,来保证只有一个实例对象存在(单列),打印ID,值一样,说明对象同一个

54、保留两位小数

题目本身只有a="%.03f"%1.3335,让计算a的结果,为了扩充保留小数的思路,提供round方法(数值,保留位数)

55、求三个方法打印结果

fn("one",1)直接将键值对传给字典;

fn("two",2)因为字典在内存中是可变数据类型,所以指向同一个地址,传了新的额参数后,会相当于给字典增加键值对

fn("three",3,{})因为传了一个新字典,所以不再是原先默认参数的字典

56、列出常见的状态码和意义

200 OK

请求正常处理完毕

204 No Content

请求成功处理,没有实体的主体返回

206 Partial Content

GET范围请求已成功处理

301 Moved Permanently

永久重定向,资源已永久分配新URI

302 Found

临时重定向,资源已临时分配新URI

303 See Other

临时重定向,期望使用GET定向获取

304 Not Modified

发送的附带条件请求未满足

307 Temporary Redirect

临时重定向,POST不会变成GET

400 Bad Request

请求报文语法错误或参数错误

401 Unauthorized

需要通过HTTP认证,或认证失败

403 Forbidden

请求资源被拒绝

404 Not Found

无法找到请求资源(服务器无理由拒绝)

500 Internal Server Error

服务器故障或Web应用故障

503 Service Unavailable

服务器超负载或停机维护

57、分别从前端、后端、数据库阐述web项目的性能优化

该题目网上有很多方法,我不想截图网上的长串文字,看的头疼,按我自己的理解说几点

前端优化:

1、减少http请求、例如制作精灵图

2、html和CSS放在页面上部,javascript放在页面下面,因为js加载比HTML和Css加载慢,所以要优先加载html和css,以防页面显示不全,性能差,也影响用户体验差

后端优化:

1、缓存存储读写次数高,变化少的数据,比如网站首页的信息、商品的信息等。应用程序读取数据时,一般是先从缓存中读取,如果读取不到或数据已失效,再访问磁盘数据库,并将数据再次写入缓存。

2、异步方式,如果有耗时操作,可以采用异步,比如celery

3、代码优化,避免循环和判断次数太多,如果多个if else判断,优先判断最有可能先发生的情况

数据库优化:

1、如有条件,数据可以存放于redis,读取速度快

2、建立索引、外键等

58、使用pop和del删除字典中的"name"字段,dic={"name":"zs","age":18}

59、列出常见MYSQL数据存储引擎

InnoDB:支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果需要对事务的完整性要求比较高(比如银行),要求实现并发控制(比如售票),那选择InnoDB有很大的优势。如果需要频繁的更新、删除操作的数据库,也可以选择InnoDB,因为支持事务的提交(commit)和回滚(rollback)。

MyISAM:插入数据快,空间和内存使用比较低。如果表主要是用于插入新记录和读出记录,那么选择MyISAM能实现处理高效率。如果应用的完整性、并发性要求比 较低,也可以使用。

MEMORY:所有的数据都在内存中,数据的处理速度快,但是安全性不高。如果需要很快的读写速度,对数据的安全性要求较低,可以选择MEMOEY。它对表的大小有要求,不能建立太大的表。所以,这类数据库只使用在相对较小的数据库表。

60、计算代码运行结果,zip函数历史文章已经说了,得出[("a",1),("b",2),("c",3),("d",4),("e",5)]

dict()创建字典新方法

61、简述同源策略

同源策略需要同时满足以下三点要求:

1)协议相同

2)域名相同

3)端口相同

http:www.test.com与https:www.test.com 不同源——协议不同

http:www.test.com与http:www.admin.com 不同源——域名不同

http:www.test.com与http:www.test.com:8081 不同源——端口不同

只要不满足其中任意一个要求,就不符合同源策略,就会出现“跨域”

62、简述cookie和session的区别

1,session 在服务器端,cookie 在客户端(浏览器)

2、session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效,存储Session时,键与Cookie中的sessionid相同,值是开发人员设置的键值对信息,进行了base64编码,过期时间由开发人员设置

3、cookie安全性比session差

63、简述多线程、多进程

进程:

1、操作系统进行资源分配和调度的基本单位,多个进程之间相互独立

2、稳定性好,如果一个进程崩溃,不影响其他进程,但是进程消耗资源大,开启的进程数量有限制

线程:

1、CPU进行资源分配和调度的基本单位,线程是进程的一部分,是比进程更小的能独立运行的基本单位,一个进程下的多个线程可以共享该进程的所有资源

2、如果IO操作密集,则可以多线程运行效率高,缺点是如果一个线程崩溃,都会造成进程的崩溃

应用:

IO密集的用多线程,在用户输入,sleep 时候,可以切换到其他线程执行,减少等待的时间

CPU密集的用多进程,因为假如IO操作少,用多线程的话,因为线程共享一个全局解释器锁,当前运行的线程会霸占GIL,其他线程没有GIL,就不能充分利用多核CPU的优势

64、简述any()和all()方法

any():只要迭代器中有一个元素为真就为真

all():迭代器中所有的判断项返回都是真,结果才为真

python中什么元素为假?

答案:(0,空字符串,空列表、空字典、空元组、None, False)

测试all()和any()方法

65、IOError、AttributeError、ImportError、IndentationError、IndexError、KeyError、SyntaxError、NameError分别代表什么异常

IOError:输入输出异常

AttributeError:试图访问一个对象没有的属性

ImportError:无法引入模块或包,基本是路径问题

IndentationError:语法错误,代码没有正确的对齐

IndexError:下标索引超出序列边界

KeyError:试图访问你字典里不存在的键

SyntaxError:Python代码逻辑语法出错,不能执行

NameError:使用一个还未赋予对象的变量

66、python中copy和deepcopy区别

1、复制不可变数据类型,不管copy还是deepcopy,都是同一个地址当浅复制的值是不可变对象(数值,字符串,元组)时和=“赋值”的情况一样,对象的id值与浅复制原来的值相同。

2、复制的值是可变对象(列表和字典)

浅拷贝copy有两种情况:

第一种情况:复制的 对象中无 复杂 子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。

第二种情况:复制的对象中有 复杂 子对象 (例如列表中的一个子元素是一个列表), 改变原来的值 中的复杂子对象的值  ,会影响浅复制的值。

深拷贝deepcopy:完全复制独立,包括内层列表和字典

67、列出几种魔法方法并简要介绍用途

__init__:对象初始化方法

__new__:创建对象时候执行的方法,单列模式会用到

__str__:当使用print输出对象的时候,只要自己定义了__str__(self)方法,那么就会打印从在这个方法中return的数据

__del__:删除对象执行的方法

68、C:\Users\ry-wu.junya\Desktop>python 1.py 22 33命令行启动程序并传参,print(sys.argv)会输出什么数据?

文件名和参数构成的列表

69、请将[i for i in range(3)]改成生成器

生成器是特殊的迭代器,

1、列表表达式的【】改为()即可变成生成器

2、函数在返回值得时候出现yield就变成生成器,而不是函数了;

中括号换成小括号即可,有没有惊呆了

70、a = "  hehheh  ",去除收尾空格

71、举例sort和sorted对列表排序,list=[0,-1,3,-10,5,9]

72、对list排序foo = [-5,8,0,4,9,-4,-20,-2,8,2,-4],使用lambda函数从小到大排序

73、使用lambda函数对list排序foo = [-5,8,0,4,9,-4,-20,-2,8,2,-4],输出结果为

[0,2,4,8,8,9,-2,-4,-4,-5,-20],正数从小到大,负数从大到小

(传两个条件,x<0和abs(x))

74、列表嵌套字典的排序,分别根据年龄和姓名排序

foo = [{"name":"zs","age":19},{"name":"ll","age":54},

        {"name":"wa","age":17},{"name":"df","age":23}]

75、列表嵌套元组,分别按字母和数字排序

76、列表嵌套列表排序,年龄数字相同怎么办?

77、根据键对字典排序(方法一,zip函数)

78、根据键对字典排序(方法二,不用zip)

有没有发现dic.items和zip(dic.keys(),dic.values())都是为了构造列表嵌套字典的结构,方便后面用sorted()构造排序规则

79、列表推导式、字典推导式、生成器

80、最后出一道检验题目,根据字符串长度排序,看排序是否灵活运用

81、举例说明SQL注入和解决办法

当以字符串格式化书写方式的时候,如果用户输入的有;+SQL语句,后面的SQL语句会执行,比如例子中的SQL注入会删除数据库demo

解决方式:通过传参数方式解决SQL注入

82、s="info:xiaoZhang 33 shandong",用正则切分字符串输出['info', 'xiaoZhang', '33', 'shandong']

|表示或,根据冒号或者空格切分

83、正则匹配以163.com结尾的邮箱

84、递归求和

85、python字典和json字符串相互转化方法

json.dumps()字典转json字符串,json.loads()json转字典

86、MyISAM 与 InnoDB 区别:

1、InnoDB 支持事务,MyISAM 不支持,这一点是非常之重要。事务是一种高

级的处理方式,如在一些列增删改中只要哪个出错还可以回滚还原,而 MyISAM

就不可以了;

2、MyISAM 适合查询以及插入为主的应用,InnoDB 适合频繁修改以及涉及到

安全性较高的应用;

3、InnoDB 支持外键,MyISAM 不支持;

4、对于自增长的字段,InnoDB 中必须包含只有该字段的索引,但是在 MyISAM

表中可以和其他字段一起建立联合索引;

5、清空整个表时,InnoDB 是一行一行的删除,效率非常慢。MyISAM 则会重

建表;

87、统计字符串中某字符出现次数

88、字符串转化大小写

89、用两种方法去空格

90、正则匹配不是以4和7结尾的手机号

91、简述python引用计数机制

python垃圾回收主要以引用计数为主,标记-清除和分代清除为辅的机制,其中标记-清除和分代回收主要是为了处理循环引用的难题。

引用计数算法

当有1个变量保存了对象的引用时,此对象的引用计数就会加1

当使用del删除变量指向的对象时,如果对象的引用计数不为1,比如3,那么此时只会让这个引用计数减1,即变为2,当再次调用del时,变为1,如果再调用1次del,此时会真的把对象进行删除

92、int("1.4"),int(1.4)输出结果?

int("1.4")报错,int(1.4)输出1

93、列举3条以上PEP8编码规范

1、顶级定义之间空两行,比如函数或者类定义。

2、方法定义、类定义与第一个方法之间,都应该空一行

3、三引号进行注释

4、使用Pycharm、Eclipse一般使用4个空格来缩进代码

94、正则表达式匹配第一个URL

findall结果无需加group(),search需要加group()提取

95、正则匹配中文

96、简述乐观锁和悲观锁

悲观锁, 就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量

97、r、r+、rb、rb+文件打开模式区别

模式较多,比较下背背记记即可

98、Linux命令重定向 > 和 >>

Linux 允许将命令执行结果 重定向到一个 文件

将本应显示在终端上的内容 输出/追加 到指定文件中

> 表示输出,会覆盖文件原有的内容

>> 表示追加,会将内容追加到已有文件的末尾

用法示例:

将 echo 输出的信息保存到 1.txt 里echo Hello Python > 1.txt 将 tree 输出的信息追加到 1.txt 文件的末尾tree >> 1.txt

99、正则表达式匹配出<html><h1>www.itcast.cn</h1></html>

前面的<>和后面的<>是对应的,可以用此方法

100、python传参数是传值还是传址?

Python中函数参数是引用传递(注意不是值传递)。对于不可变类型(数值型、字符串、元组),因变量不能修改,所以运算不会影响到变量自身;而对于可变类型(列表字典)来说,函数体运算可能会更改传入的参数变量。

101、求两个列表的交集、差集、并集

102、生成0-100的随机数

random.random()生成0-1之间的随机小数,所以乘以100

103、lambda匿名函数好处

精简代码,lambda省去了定义函数,map省去了写for循环过程

104、常见的网络传输协议

UDP、TCP、FTP、HTTP、SMTP等等

105、单引号、双引号、三引号用法

1、单引号和双引号没有什么区别,不过单引号不用按shift,打字稍微快一点。表示字符串的时候,单引号里面可以用双引号,而不用转义字符,反之亦然。

'She said:"Yes." ' or  "She said: 'Yes.' "

2、但是如果直接用单引号扩住单引号,则需要转义,像这样:

' She said:\'Yes.\' '

3、三引号可以直接书写多行,通常用于大段,大篇幅的字符串

"""

hello

world

"""

106、python垃圾回收机制

python垃圾回收主要以引用计数为主,标记-清除和分代清除为辅的机制,其中标记-清除和分代回收主要是为了处理循环引用的难题。

引用计数算法

当有1个变量保存了对象的引用时,此对象的引用计数就会加1

当使用del删除变量指向的对象时,如果对象的引用计数不为1,比如3,那么此时只会让这个引用计数减1,即变为2,当再次调用del时,变为1,如果再调用1次del,此时会真的把对象进行删除

107、HTTP请求中get和post区别

1、GET请求是通过URL直接请求数据,数据信息可以在URL中直接看到,比如浏览器访问;而POST请求是放在请求头中的,我们是无法直接看到的;

2、GET提交有数据大小的限制,一般是不超过1024个字节,而这种说法也不完全准确,HTTP协议并没有设定URL字节长度的上限,而是浏览器做了些处理,所以长度依据浏览器的不同有所不同;POST请求在HTTP协议中也没有做说明,一般来说是没有设置限制的,但是实际上浏览器也有默认值。总体来说,少量的数据使用GET,大量的数据使用POST。

3、GET请求因为数据参数是暴露在URL中的,所以安全性比较低,比如密码是不能暴露的,就不能使用GET请求;POST请求中,请求参数信息是放在请求头的,所以安全性较高,可以使用。在实际中,涉及到登录操作的时候,尽量使用HTTPS请求,安全性更好。

108、python中读取Excel文件的方法

应用数据分析库pandas

109、简述多线程、多进程

进程:

1、操作系统进行资源分配和调度的基本单位,多个进程之间相互独立

2、稳定性好,如果一个进程崩溃,不影响其他进程,但是进程消耗资源大,开启的进程数量有限制

线程:

1、CPU进行资源分配和调度的基本单位,线程是进程的一部分,是比进程更小的能独立运行的基本单位,一个进程下的多个线程可以共享该进程的所有资源

2、如果IO操作密集,则可以多线程运行效率高,缺点是如果一个线程崩溃,都会造成进程的崩溃

应用:

IO密集的用多线程,在用户输入,sleep 时候,可以切换到其他线程执行,减少等待的时间

CPU密集的用多进程,因为假如IO操作少,用多线程的话,因为线程共享一个全局解释器锁,当前运行的线程会霸占GIL,其他线程没有GIL,就不能充分利用多核CPU的优势

110、python正则中search和match

 

归类 : 面试题收集&整理

 
posted @ 2019-11-11 21:48  Py猫的故事  阅读(1137)  评论(0编辑  收藏  举报
返回顶部