1、讲述下scrapy框架,并阐述工作流程:
ScrapyEngine(
引擎
)
:负责Spider
、ItemPipeline
、Downloader
、Scheduler
中间的通讯,信号、数据传递等。
-
Scheduler(
调度器
)
:它负责接受引擎
发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎
需要时,交还给引擎
。 -
Downloader
(下载器)
:负责下载ScrapyEngine(
引擎
)
发送的所有Requests请求,并将其获取到的Responses交还给ScrapyEngine(
引擎
)
,由引擎
交给Spider
来处理, -
Spider
(爬虫)
:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎
,再次进入Scheduler(
调度器
)
, -
ItemPipeline(
管道
)
:它负责处理Spider
中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方. -
DownloaderMiddlewares
(下载中间件)
:你可以当作是一个可以自定义扩展下载功能的组件。 -
SpiderMiddlewares
(
Spider
中间件)
:你可以理解为是一个可以自定扩展和操作引擎
和Spider
中间通信
的功能组件(比如进入Spider
的Responses;和从Spider
出去的Requests)
2、简单介绍:
engine将spider中的url提取出,将requests请求交给scheddler调度器排序入队列并返还给engine,engine将requests按照donwloder的Downloadermiddleware下载中间件的设置,下载一下requests请求;
然后,下载器将下载好的东西给engine,(要是下载失败,就告诉engine,从而告诉调度器scheddler,requests下载失败,记录一下,而后在下载)
然后engine将response给到spiders(注意!这儿responses默认是交给defparse()
这个函数处理的)、
而后,spiders处理完毕数据之后对于需要跟进的URL交还给engine重新路队列,并将item数据交给engine
最后,itempipeline将engine拿到的item数据进行相应的后期处理,比如详细解析、过滤、存储
3、熟悉的解析工具:xpath、正则、beautifulsoup、pq
正则的使用:
用来处理字符串的方法re.findall re.search re.sub
正则字符串编译成正则表达式对象compile()
Xpath:全称XMLPath Language,即XML路径语言,它是一门在XML文档中查找信息的语言。
如/代表选取直接子节点,//代表选择所有子孙节点,.代表选取当前节点,..代表选取当前节点的父节点,@则是加了属性的限定,选取匹配属性的特定节点。
<span style="color:#333333"><code>//title[@lang=’eng’]</code></span>
这就是一个 XPath规则,它就代表选择所有名称为title,同时属性lang的值为 eng的节点。
属性获取和属性匹配的方法不同
属性匹配是中括号加属性名和值来限定某个属性,如[@href="link1.html"],而此处的
@href指的是获取节点的某个属性
属性多值匹配class="lili-first"
<span style="color:#333333"><code> </code><code>html.xpath('//li[contains(@class, "li")]/a/text()')</code></span>
多属性匹配<liclass="li li-first" name="item">
<span style="color:#333333"><code>html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')</code></span>
按序选择,选择的时候可能某些属性同时匹配了多个节点
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
html.xpath('//li[1]/a/text()')
第一次选择我们选取了第一个li节点,中括号中传入数字1即可,注意这里和代码中不同,序号是以1开头的,不是0开头的。
response.xpath('//div[@class="c" and contains(@id, "M_")]')
weibo.xpath('.//a[contains(., "原论")]//@href').extract_first()
weibo.xpath('.//a[contains(., "原论")]//@href').extract_first()
Beautifulsoup
方法选择器find_all()
soup.find_all(name='ul')
soup.find_all(attrs={'id': 'list-1'})
CSS
选择器
soup.select('.panel .panel-heading')
soup.select('#list-2 .element')
获取属性
soup.select(ul['id'])
response.css(
'li.next a::attr(data-key)'
).extract_first()
good.css(
'div.row.row-2.title a.J_ClickStat::text'
).extract()
4、熟悉的爬虫包:requests、bs4、pyspider
requests
抓取网页
requests.get("https://www.zhihu.com/explore",headers=headers)
抓取二进制数据
r = requests.get("https://github.com/favicon.ico")
print(r.text)
print(r.content)
POST请求
data = {'name': 'germey', 'age': '22'}
r = requests.post("http://httpbin.org/post", data=data)
4.SSL证书验证
requests.get('https://www.12306.cn', verify=False)
5、加代理怎么加:
1、简单的代理设置
proxies = {
'http': 'socks5://user:password@host:port',
'https': 'socks5://user:password@host:port'
}
proxy = { "http": "mr_mao_hacker:sffqry9r@61.158.163.130:16816" }
requests.get('https://www.taobao.com', proxies=proxies)
4.
常见代理设置
-
使用网上的免费代理,最好使用高匿代理,使用前抓取下来筛选一下可用代理,也可以进一步维护一个代理池。
-
使用付费代理服务,互联网上存在许多代理商,可以付费使用,质量比免费代理好很多。
-
ADSL拨号,拨一次号换一次IP,稳定性高,也是一种比较有效的解决方案。
6、mongo、redis、mysql数据库的基本操作
7、http、https的区别
HTTP叫做超文本传输协议,
被用于在web浏览器和网站服务器之间传递信息
http协议工作是以明文方式发送内容,不提供任何形式的数据加密
http协议不适合传输一些重要的、敏感的信息,比如信用卡密码及支付验证码等。
HTTPS(HypertextTransfer Protocol over Secure Socket Layer)
简单讲是HTTP的安全版,在HTTP下加入SSL层。SSL主要用于Web的安全传输协议,在传输层对网络连接进行加密,保障在Internet上数据传输的安全。
https协议需要到ca申请证书
http是超文本传输协议,信息明文传输;https是http与ssl加密传输的
http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。
http连接无状态;而https连接是应用http与ssl协议进行连接,可以进行加密传输与身份验证等服务,比http更加安全
无状态指协议对于事务处理没有记忆能力
浏览器发送HTTP请求的过程:
1、用户输入url回车后,浏览器发送一个Request请求,服务器把Response文件对象发送回给浏览器,浏览器分析Response中的HTML,里面有好多东西,当所有的文件都下载成功后,网页会根据HTML语法结构,完整的显示。
HTTP响应也由四个部分组成,分别是: 状态行
、消息报头
、空行
、响应正文
Cookie:通过在客户端 记录的信息确定用户的身份。
Session:通过在服务器端 记录的信息确定用户的身份。
RequestHeaders
请求头,用来说明服务器要使用的附加信息,比较重要的信息有Cookie、Referer、User-Agent等,下面将一些常用的头信息说明如下:
-
Accept,请求报头域,用于指定客户端可接受哪些类型的信息。
-
Accept-Language,指定客户端可接受的语言类型。
-
Accept-Encoding,指定客户端可接受的内容编码。
-
Host,用于指定请求资源的主机IP和端口号,其内容为请求URL的原始服务器或网关的位置。从HTTP1.1 版本开始,Request必须包含此内容。
-
Cookie,也常用复数形式Cookies,是网站为了辨别用户进行Session跟踪而储存在用户本地的数据。Cookies的主要功能就是维持当前访问会话,例如我们输入用户名密码登录了某个网站,登录成功之后服务器会用Session保存我们的登录状态信息,后面我们每次刷新或请求该站点的其他页面时会发现都是保持着登录状态的,在这里就是Cookies的功劳,Cookies里有信息标识了我们所对应的服务器的Session会话,每次浏览器在请求该站点的页面时都会在请求头中加上Cookies并将其发送给服务器,服务器通过Cookies识别出是我们自己,并且查出当前状态是登录的状态,所以返回的结果就是登录之后才能看到的网页内容。
-
Referer,此内容用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如做来源统计、做防盗链处理等。
-
User-Agent,简称UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、浏览器及版本等信息。在做爬虫时加上此信息可以伪装为浏览器,如果不加很可能会被识别出为爬虫。
-
Content-Type,即InternetMedia Type,互联网媒体类型,也叫做MIME类型,在 HTTP协议消息头中,使用它来表示具体请求中的媒体类型信息。例如text/html代表 HTML格式,image/gif代表 GIF图片,application/json代表 Json类型,更多对应关系可以查看此对照表:http://tool.oschina.net/commons。
RequestBody
即请求体,一般承载的内容是 POST请求中的 FormData,即表单数据,而对于GET请求 RequestBody 则为空。
8、get、post的区别
Get 在url中传递数据,数据信息放在请求头中;而post请求信息放在请求体中进行传递数据;
get传输数据的数据量较小,只能在请求头中发送数据。Post传输数据信息比较大,一般不受限制;
Get数据传输安全性低,post传输数据安全性高
在执行效率来说,get比post好
9、多线程 多进程 协程
从操作系统的角度:
进程和线程,都是一种CPU的执行单元。
进程:表示一个程序的上下文执行活动(打开、执行、保存...)
线程:进程执行程序时候的最小调度单位(执行a,执行b...)
一个程序至少有一个进程,一个进程至少有一个线程。
并行和 并发:
并行:多个CPU核心,不同的程序就分配给不同的CPU来运行。可以让多个程序同时执行。
cpu1-------------
cpu2 -------------
cpu3 -------------
cpu4-------------
并发:单个CPU核心,在一个时间切片里一次只能运行一个程序,如果需要运行多个程序,则串行执行。
cpu1 ---- ----
cpu1 ---- ----
多进程/多线程:
表示可以同时执行多个任务,进程和线程的调度是由操作系统自动完成。
进程:每个进程都有自己独立的内存空间,不同进程之间的内存空间不共享。
进程之间的通信有操作系统传递,导致通讯效率低,切换开销大。
线程:一个进程可以有多个线程,所有线程共享进程的内存空间,通讯效率高,切换开销小。
共享意味着竞争,导致数据不安全,为了保护内存空间的数据安全,引入"互斥锁"。
一个线程在访问内存空间的时候,其他线程不允许访问,必须等待之前的线程访问结束,才能使用这个内存空间。
互斥锁:一种安全有序的让多个线程访问内存空间的机制。
所以:
多进程:密集CPU任务,需要充分使用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。multiprocessing
缺陷:多个进程之间通信成本高,切换开销大。
多线程:密集I/O任务(网络I/O,磁盘I/O,数据库I/O)使用多线程合适。
threading.Thread、multiprocessing.dummy
缺陷:同一个时间切片只能运行一个线程,不能做到高并行,但是可以做到高并发。
协程:又称微线程,在单线程上执行多个任务,用函数切换,开销极小。不通过操作系统调度,没有进程、线程的切换开销。genvent,monkey.patchall
多线程请求返回是无序的,那个线程有数据返回就处理那个线程,而协程返回的数据是有序的。
缺陷:单线程执行,处理密集CPU和本地磁盘IO的时候,性能较低。处理网络I/O性能还是比较高.
d
下面以这个网站为例,采用三种方式爬取。爬取前250名的电影。。
解释:
1.协程使用生成器函数定义:定义体中有yield关键字。
2.yield 在表达式中使用;如果协程只需从客户那里接收数据,那么产出的值是None—— 这个值是隐式指定的,因为yield关键字右边没有表达式。
3.首先要调用next(…)函数,因为生成器还没启动,没在yield语句处暂停,所以一开始无法发送数据。
4.调用send方法,把值传给yield的变量,然后协程恢复,继续执行下面的代码,直到运行到下一个yield表达式,或者终止。
==注意:send方法只有当协程处于GEN_SUSPENDED状态下时才会运作,所以我们使用next()方法激活协程到yield表达式处停止,或者我们也可以使用sc.send(None),效果与next(sc)一样==。
defsimple_coroutine():
rint('->start')
x=yield
rint('->recived',x)
sc= simple_coroutine() next(sc) sc.send('zhexiao')
10、scrapy——redis分布式基础
11、反爬虫措施
通过headers反爬虫:解决策略,伪造headers
基于用户行为反爬虫:动态变化去爬取数据,模拟普通用户的行为
基于动态页面的反爬虫:跟踪服务器发送的ajax请求,模拟ajax请求
headers反爬虫:解决策略,伪造headers
基于用户行为反爬虫:动态变化去爬取数据,模拟普通用户的行为
基于动态页面的反爬虫:跟踪服务器发送的ajax请求,模拟ajax请求
cookies爬虫
ip爬虫
验证码
2.1通过Headers反爬虫
从用户请求的Headers反爬虫是最常见的反爬虫策略。由于正常用户访问网站时是通过浏览器访问的,所以目标网站通常会在收到请求时校验Headers中的User-Agent字段,如果不是携带正常的User-Agent信息的请求便无法通过请求。还有一部分网站为了防盗链,还会校验请求Headers中的Referer字段。
如果遇到了这类反爬虫机制,可以直接在自己写的爬虫中添加Headers,将浏览器的User-Agent复制到爬虫的Headers中;另外通过对请求的抓包分析,将Referer值修改为目标网站域名,就能很好的绕过。
2.2基于用户行为反爬虫
还有一些网站会通过用户的行为来检测网站的访问者是否是爬虫,例如同一IP短时间内多次访问同一页面,或者同一账户短时间内多次进行相同操作。
大多数网站都是前一种情况,对于这种情况有两种策略:
1)使用代理ip。例如可以专门写一个在网上抓取可用代理ip的脚本,然后将抓取到的代理ip维护到代理池中供爬虫使用,当然,实际上抓取的ip不论是免费的还是付费的,通常的使用效果都极为一般,如果需要抓取高价值数据的话也可以考虑购买宽带adsl拨号的VPS,如果ip被目标网站被封掉,重新拨号即可。
2)降低请求频率。例如每个一个时间段请求一次或者请求若干次之后sleep一段时间。由于网站获取到的ip是一个区域网的ip,该ip被区域内的所有人共享,因此这个间隔时间并不需要特别长
对于第二种情况,可以在每次请求后随机间隔几秒再进行下一次请求。对于有逻辑漏洞的网站,可以通过请求几次,退出登录,重新登录,继续请求来绕过同一账号短时间内不能多次进行相同请求的限制,如果能有多个账户,切换使用,效果更佳。
2.3动态页面的反爬虫
上述的几种情况大多都是出现在静态页面,但是对于动态网页,我们需要爬取的数据是通过ajax请求得到,或者通过JavaScript生成的。首先用Firebug或者HttpFox对网络请求进行分析。如果能够找到ajax请求,也能分析出具体的参数和响应的具体含义,我们就能采用上面的方法,直接利用requests或者urllib2模拟ajax请求,对响应的json进行分析得到需要的数据。
能够直接模拟ajax请求获取数据固然是极好的,但是有些网站把ajax请求的所有参数全部加密了。我们根本没办法构造自己所需要的数据的请求。还有一些严防死守的网站,除了加密ajax参数,它还把一些基本的功能都封装了,全部都是在调用自己的接口,而接口参数都是加密的。
遇到这样的网站,我们就不能用上面的方法了,通过selenium+phantomJS框架,调用浏览器内核,并利用phantomJS执行js来模拟人为操作以及触发页面中的js脚本。从填写表单到点击按钮再到滚动页面,全部都可以模拟,不考虑具体的请求和响应过程,只是完完整整的把人浏览页面获取数据的过程模拟一遍。用这套框架几乎能绕过大多数的反爬虫,因为它不是在伪装成浏览器来获取数据(上述的通过添加Headers一定程度上就是为了伪装成浏览器),它本身就是浏览器,phantomJS就是一个没有界面的浏览器,只是操控这个浏览器的不是人。
2.4Cookie限制
和Headers校验的反爬虫机制类似,当用户向目标网站发送请求时,会再请求数据中携带Cookie,网站通过校验请求信息是否存在Cookie,以及校验Cookie的值来判定发起访问请求的到底是真实的用户还是爬虫,第一次打开网页会生成一个随机cookie,如果再次打开网页这个Cookie不存在,那么再次设置,第三次打开仍然不存在,这就非常有可能是爬虫在工作了。
而Cookie校验和Headers的区别在于,用户发送的Headers的内容形式是固定的可以被轻易伪造的,Cookie则不然。原因是由于,我们在分析浏览器请求网站访问的过程中所分析得到的Cookie往往都是经过相关的js等过程已经改变了domain的Cookie,假如直接手动修改爬虫携带的Cookie去访问对应的网页,由于携带的Cookie已经是访问之后的domain而不是访问之前的domain,所以是无法成功模拟整个流程的,这种情况必然导致爬虫访问页面失败。
分析Cookie,可能会携带大量的随机哈希字符串,或者不同时间戳组合的字符串,并且会根据每次访问更新domain的值。对这种限制,首先要在对目标网站抓包分析时,必须先清空浏览器的Cookie,然后在初次访问时,观察浏览器在完成访问的过程中的请求细节(通常会在这一过程中发生若干次301/302转跳,每次转跳网站返回不同的Cookie给浏览器然后在最后一次转跳中请求成功)。在抓包完成对请求细节的分析之后,再在爬虫上模拟这一转跳过程,然后截取Cookie作为爬虫自身携带的Cookie,这样就能够绕过Cookie的限制完成对目标网站的访问了。
2.5验证码限制
这是一个相当古老但却不失有效性的反爬虫策略。更早的时候,这种验证码可以通过OCR技术进行简单的图像识别破解,但是现在来说,验证码的干扰线,噪点已经多到肉眼都无法轻易识别的地步。所以目前而言,由于OCR技术发展不力,验证码技术反而成为了许多网站最有效的手段之一。
验证码除了识别难题之外,还有另外一个值得注意的问题。现在有许多网站都在使用第三方验证码服务。当用户打开目标网站的登录页面时,登录页面显示的验证码是从第三方(比如阿里云)提供的链接加载的,这时候我们在模拟登录的时候,需要多一步从网页提供的第三方链接抓取验证码的步骤,而这一步常常暗含着陷阱。以阿里云提供的验证码服务为例,登录页面的源代码会显示阿里云提供的第三方链接,但是当匹配出这个链接进行验证码抓取的时候我们会发现验证码是无效的。当仔细分析抓包的请求数据之后,发现正常浏览器在请求验证码时,会多带一个ts参数,而这个参数是由当前时间戳产生的,但是并不是完全的时间戳,而是时间戳四舍五入保留九位数字之后的字符串,对待这种第三方服务只能是细心加运气,三分天注定七分不信命来猜一发了。还有另外一种特殊的第三方验证码,所谓的拖动验证,只能说,互联网创业有三种模式2b,2c,2vc。
12、遇到最难的反扒是什么
13、讲述你最近的一个项目
14、静态网站与动态网站的处理
静态网页
网页的内容是HTML代码编写的,文字、图片等内容均是通过写好的HTML代码来指定的
网页加载速度快,编写简单,但是存在很大的缺陷,如可维护性差
动态网页
它可以动态解析URL中参数的变化,关联数据库并动态地呈现不同的页面内容,功能相比静态网页强大和丰富太多
两个用于保持HTTP连接状态的技术就出现了,它们分别是Session和Cookies,Session在服务端,也就是网站的服务器,用来保存用户的会话信息,Cookies在客户端,也可以理解为浏览器端
15、算法知识
16、怎么判断python是否在工作 监控
https://blog.youkuaiyun.com/qq_37634812/article/details/77646316
17、同步和异步区别
异步:多任务, 多个任务之间执行没有先后顺序,可以同时运行,执行的先后顺序不会有什么影响,存在的多条运行主线
同步:多任务, 多个任务之间执行的时候要求有先后顺序,必须一个先执行完成之后,另一个才能继续执行,只有一个主线
阻塞:从调用者的角度出发,如果在调用的时候,被卡住,不能再继续向下运行,需要等待,就说是阻塞
非阻塞:从调用者的角度出发, 如果在调用的时候,没有被卡住,能够继续向下运行,无需等待,就说是非阻塞
tips:
-
await表达式中的对象必须是awaitable
-
requests不支持非阻塞
-
aiohttp是用于异步请求的库
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
同步阻塞
异步非阻塞
18、html的构成
在HTML中,所有标签定义的内容都是节点,它们构成了一个HTMLDOM 树。
HTMLDOM 将HTML文档视作树结构,这种结构被称为节点树,如图2-12所示:
另外 CSS选择器还有一些其他的语法规则,在这里整理如下:
选择器 | 例子 | 例子描述 |
---|---|---|
.class | .intro | 选择 class="intro"的所有节点。 |
#id | #firstname | 选择 id="firstname"的所有节点。 |
* | * | 选择所有节点。 |
element | p | 选择所有 p节点。 |
element,element | div,p | 选择所有 div节点和所有 p节点。 |
elementelement | divp | 选择 div节点内部的所有 p节点。 |
element>element | div>p | 选择父节点为 div节点的所有 p节点。 |
18、数据基础,怎么简单去重
在python相关职位的面试过程中,会对列表list的去重进行考察。(注意有时会要求保证去重的顺序性)
1、直观方法
1 li=[1,2,3,4,5,1,2,3]
2 new_li=[]
3 for i in li:
4 if i not in new_li:
5 new_li.append(i)
6 print(new_li)
2 new_li=[]
3 for i in li:
4 if i not in new_li:
5 new_li.append(i)
6 print(new_li)
先建立一个新的空列表,通过遍历原来的列表,再利用逻辑关系notin 来去重。
总结:这样可以做出来,但是过程不够简单。但是此方法保证了列表的顺序性。
2、利用set的自动去重功能
1 li=[1,2,3,4,5,1,2,3]
2 li=list(set(li))
3 print(li)
2 li=list(set(li))
3 print(li)
将列表转化为集合再转化为列表,利用集合的自动去重功能。简单快速。缺点是:使用set方法无法保证去重后的顺序。
但是,可以通过列表中索引(index)的方法保证去重后的顺序不变。
1 li=[1,2,3,4,5,1,2,3]
2 new_li=list(set(li))
3 new_li.sort(key=li.index)
4 print(new_li)
2 new_li=list(set(li))
3 new_li.sort(key=li.index)
4 print(new_li)
以上两种list去重方法就是常用的去重方法,需要注意的是面试时如果要求保证列表的顺序时,注意set方法通过索引也能保证顺序性。
19、可变对象和不可变对象
可变值得是对象生成后可以改变,但地址不会变 list、dict
不可变指的是对象创建后便不能改变,如果改变会指向一个新的对象
str、tuple、int、float、
20、tryexcept的使用
21、状态吗
300 | 多种选择 | 针对请求,服务器可执行多种操作。 |
301 | 永久移动 | 请求的网页已永久移动到新位置,即永久重定向。 |
302 | 临时移动 | 请求的网页暂时跳转到其他页面,即暂时重定向。 |
303 | 查看其他位置 | 如果原来的请求是 POST,重定向目标文档应该通过GET提取。 |
304 | 未修改 | 此次请求返回的网页未修改,继续使用上次的资源。 |
305 | 使用代理 | 请求者应该使用代理访问该网页。 |
307 | 临时重定向 | 请求的资源临时从其他位置响应。 |
400 | 错误请求 | 服务器无法解析该请求。 |
401 | 未授权 | 请求没有进行身份验证或验证未通过。 |
403 | 禁止访问 | 服务器拒绝此请求。 |
404 | 未找到 | 服务器找不到请求的网页。 |
405 | 方法禁用 | 服务器禁用了请求中指定的方法。 |
406 | 不接受 | 无法使用请求的内容响应请求的网页。 |
407 | 需要代理授权 | 请求者需要使用代理授权。 |
408 | 请求超时 | 服务器请求超时。 |
409 | 冲突 | 服务器在完成请求时发生冲突。 |
410 | 已删除 | 请求的资源已永久删除。 |
411 | 需要有效长度 | 服务器不接受不含有效内容长度标头字段的请求。 |
412 | 未满足前提条件 | 服务器未满足请求者在请求中设置的其中一个前提条件。 |
413 | 请求实体过大 | 请求实体过大,超出服务器的处理能力。 |
414 | 请求 URI过长 | 请求网址过长,服务器无法处理。 |
415 | 不支持类型 | 请求的格式不受请求页面的支持。 |
416 | 请求范围不符 | 页面无法提供请求的范围。 |
417 | 未满足期望值 | 服务器未满足期望请求标头字段的要求。 |
500 | 服务器内部错误 | 服务器遇到错误,无法完成请求。 |
501 | 未实现 | 服务器不具备完成请求的功能。 |
502 | 错误网关 | 服务器作为网关或代理,从上游服务器收到无效响应。 |
503 | 服务不可用 | 服务器目前无法使用。 |
504 | 网关超时 | 服务器作为网关或代理,但是没有及时从上游服务器收到请求。 |
505 | HTTP版本不支持 | 服务器不支持请求中所用的 HTTP协议版本。 |
1、__slots__
动态语言:可以在运行的过程中,修改代码
静态语言:编译时已经确定好代码,运行过程中不能修改
class Person(object):
__slots__ = ("name", "age")
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
2、生成器
通过列表生成式,我们可以直接创建一个列表。
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建生成器方法
第一种方法很简单,只要把一个列表生成式的[] 改成()
L = [ x*2 for x in range(5)]
生成式
G = ( x*2 for x in range(5))
生成器
正确的方法是使用
for
循环,因为生成器也是可迭代对象。
G = ( x*2 for x in range(5))
生成器
正确的方法是使用
for
循环,因为生成器也是可迭代对象。
创建生成器方法
2
generator
非常强大。如果推算的算法比较复杂,用类似列表生成式的
for
循环无法实现的时候,还可以用函数来实现。
In [28]: def fib(times):
....: n = 0
....: a,b = 0,1
....: while n<times:
....: print(b)
....: a,b = b,a+b
....: n+=1
....: return 'done'
也就是说,上面的函数和
generator
仅一步之遥。要把
fib
函数变成
generator
,只需要把
print(b)
改为
yield b
就可以了:
生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第
n
次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
生成器的特点:
-
节约内存
-
迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的
3、迭代器
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
可迭代对象
以直接作用于
for
循环的数据类型有以下几种:
一类是集合数据类型,如 list、 tuple、 dict、 set、 str等;
一类是 generator,包括生成器和带yield的generatorfunction。
这些可以直接作用于 for循环的对象统称为可迭代对象:Iterable。
2.
判断是否可以迭代
可以使用 isinstance()判断一个对象是否是Iterable对象
3.
迭代器
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用 isinstance()判断一个对象是否是Iterator对象:
In [57]: isinstance((x for x in range(10)), Iterator)
Out[57]: True
4.iter()
函数
生成器都是 Iterator对象,但 list、 dict、 str虽然是 Iterable,却不是 Iterator。
把 list、 dict、 str等 Iterable变成 Iterator可以使用 iter()函数:
isinstance(iter('abc'), Iterator)
总结
-
凡是可作用于for循环的对象都是Iterable类型;
-
凡是可作用于next()函数的对象都是Iterator类型
-
集合数据类型如 list、 dict、 str等是 Iterable但不是 Iterator,不过可以通过iter()函数获得一个 Iterator对象。
-
4
、
闭包
2.
什么是闭包
#
定义一个函数
def test(number):
#
在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变
量称之为闭包
def test_in(number_in):
print("in test_in
函数
, number_in is %d"%number_in)
return number+number_in
#
其实这里返回的就是闭包的结果
return test_in
#
给
test
函数赋值,这个
20
就是给参数
number
ret = test(20)
#
注意这里的
100
其实给参数
number_in
print(ret(100))
3.
闭包再理解
内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。
4.
看一个闭包的实际例子:
def line_conf(a, b):
def line(x):
return a*x + b
return line
line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
闭包思考:
1.
闭包似优化了变量,原来需要类对象完成的工作,闭包也可以完成
2.
由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存
闭包也具有提高代码可复用性的作用
5
、装饰器,开发效率如虎添翼
开放封
写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
-
封闭:已实现的功能代码块
-
开放:对扩展开发
def w1(func):
def inner():
#
验证
1
#
验证
2
#
验证
3
func()
return inner
@w1
def f1():
print('f1')
@函数名是python的一种语法糖。
def inner():
#
验证
1
#
验证
2
#
验证
3
f1() # func
是参数,此时
func
等于
f1
return inner#
返回的
inner
,
inner
代表的是函数,非执行函数
,
其实就是将原来的
f1
函数塞进另外一个函数中
将执行完的w1函数返回值赋值 给@w1下面的函数的函数名f1即将w1的返回值再重新赋值给f1,即:
新
f1 = def inner():
#
验证
1
#
验证
2
#
验证
3
原来
f1()
return inner
6.到底什么是元类(终于到主题了)
元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。
元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:
7、
is是比较两个引用是否指向了同一个对象(引用比较)。
-
==是比较两个对象是否相等。
8、浅拷贝
-
浅拷贝是对于一个对象的顶层拷贝
深拷贝
-
深拷贝是对于一个对象所有层次的拷贝(递归)
9、私有化
-
xx:公有变量
-
_x:单前置下划线,私有化属性或方法,fromsomemodule import *禁止导入,类对象和子类可以访问
-
__xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)
-
__xx__:双前后下划线,用户名字空间的魔法对象或属性。例如:
__init__
,__ 不要自己发明这样的名字 -
xx_:单后置下划线,用于避免与Python关键词的冲突
10、1.Garbage collection(GC垃圾回收)
现在的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的方式。自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄露,悬空指针等bug埋下隐患。对于一个字符串、列表、类甚至数值都是对象,且定位简单易用的语言,自然不会让用户去处理如何分配回收内存的问题。python里也同java一样采用了垃圾收集机制,不过不一样的是:python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略
一.垃圾回收机制
Python中的垃圾回收是以引用计数为主,分代收集为辅。
1、导致引用计数+1的情况
-
对象被创建,例如
a=23
-
对象被引用,例如
b=a
-
对象被作为参数,传入到一个函数中,例如
func(a)
-
对象作为一个元素,存储在容器中,例如
list1=[a,a]
2、导致引用计数-1的情况
-
对象的别名被显式销毁,例如
dela
-
对象的别名被赋予新的对象,例如
a=24
-
一个对象离开它的作用域,例如
f
函数执行完毕时,
func
函数中的局部变量(全局变量不会)
-
对象所在的容器被销毁,或从容器中删除对象
3、查看一个对象的引用计数
import sys
a = "hello world"
sys.getrefcount(a)
可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
12、3.什么是socket
socket(简称 套接字
)是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:
它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于Socket来完成通信的
例如我们每天浏览网页、QQ聊天、收发 email等等
socket.socket(AddressFamily, Type)
如何拿到半数面试公司Offer——我的Python求职之路
从八月底开始找工作,短短的一星期多一些,面试了9家公司,拿到5份Offer,可能是因为我所面试的公司都是些创业性的公司吧,不过还是感触良多,因为学习Python的时间还很短,没想到还算比较容易的找到了工作,就把这些天的面试经验和大家分享一下,希望为学习Python找工作的小伙伴们提供些许帮助。
笔者感觉面试最主要的两点:1.项目经验。2.项目经验和招聘职位相符,这是最主要的,其他的都是锦上添花。
自我介绍
这是一道送分题,万年不变的第一个问题。不过有些小伙伴可能没有太在意,其实这个问题已经在面试官心中决定了你的去留意向。自我介绍的主要结构:个人基本信息+基本技术构成+项目经验(具体项目以及在项目中的负责部分)+自我评价,其中的原则就是紧紧围绕招聘岗位的需求做介绍。在此之前要做好准备工作,看看招聘方具体需要什么方向的研发工程师。目前针对Python,拉勾上的招聘多为自动化测试平台的设计与开发、数据的挖掘与清洗。单纯的web开发好像还没有,所以web方向的同学注意,多和运维以及自动化方面靠拢。
二段式询问
在面试的过程当中,在面试官提出问题的时候,往往会就问题本身引申出较深层次的问题。比如:你使用过with语句吗?我的回答是:with语句经常适用于对资源进行访问的场合,确保在访问的过程中不管是否发生异常都会指执行必要的清理操作,比如文件的自动关闭以及线程中锁的自动获取与释放。面试官紧接着问,那你知道为什么with语句能够使文件正确关闭,一下子把我问闷了,只能依稀记得with语句会开辟出一块独立环境来执行文件的访问,类似沙盒机制。面试官对这个答案不置可否,算是勉强通过了。所以知其然更要知其所以然。在平时的学习中,多问一个为什么,面试的时候就不会太被动。
不要给自己挖坑
确保你在回答面试官的过程中,回答中的每个知识点都了然于胸,不然被问住,是很难堪的。我在回答web安全问题时,顺嘴说了SQL注入,面试官说既然提到了SQL注入,那么你讲讲它的原理及解决方法吧!丢脸的是我竟然把XSS跨站注入攻击和SQL注入搞混了,场面也是有点尴尬。所以斟酌你说的每一句话,聪明点的同学还可以引导面试官,让他问出自己想要被问的问题。
必问到Redis,高并发解决办法
面试了好多家公司,必然问道Redis了解多少,高并发的解决办法。笔者回答的都不是很好。
这一年你学习了什么新的技能
这是面试官在考察你是否对于新鲜技术抱有极大热忱。面试我的面试官无一例外都问到了这个问题。他们都希望能找一个不断学习,开括创新的年轻人。多浏览最新的技术资讯,选择一方面自己感兴趣的领域。
你会选择创业公司还是像BAT那样的大公司,为什么?
当然是看招聘方属于哪一个公司啦,不过问这种问题的一般都是创业公司。答案无非是:挑战大,享受挑战;创业公司具有无限成功的可能性,想随公司一起成长;
为什么你要从上一家公司离职?
这也是一个必问问题,找一个比较正当的理由,不要说什么公司零食太多胖了20斤,公司周别附近的外卖都吃腻了,真的别这样说…主要原则就是不要对前公司抱有怨言,BOSS朝令夕改,PM不靠谱什么的,多寻找自身原因:公司发展比较稳定,但我还年轻,希望有更大的挑战和更多的学习机会。像这样就可以。
描述一下你的上一家公司
这个问题问到的几率不太大,不过也还是有三家公司问到过,招聘方主要想从上一家公司的具体经营规模以及主营业务来定位你的水平,知道招聘方的目的就可以从容应答。
技术性问题
非技术性的问题就是以上这么多,作为参考稍加准备,面试的时候就能对答如流。下面讲一下在面试中的技术性问题。个人感觉技术性的问题面试官问的没有特别多,一般考察2-3个,由浅到深。
-
简述函数式编程
在函数式编程中,函数是基本单位,变量只是一个名称,而不是一个存储单元。除了匿名函数外,Python还使用fliter(),map(),reduce(),apply()函数来支持函数式编程。
-
什么是匿名函数,匿名函数有什么局限性
匿名函数,也就是lambda函数,通常用在函数体比较简单的函数上。匿名函数顾名思义就是函数没有名字,因此不用担心函数名冲突。不过Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。
-
如何捕获异常,常用的异常机制有哪些?
如果我们没有对异常进行任何预防,那么在程序执行的过程中发生异常,就会中断程序,调用python默认的异常处理器,并在终端输出异常信息。
try...except...finally语句:当try语句执行时发生异常,回到try语句层,寻找后面是否有except语句。找到except语句后,会调用这个自定义的异常处理器。except将异常处理完毕后,程序继续往下执行。finally语句表示,无论异常发生与否,finally中的语句都要执行。
assert语句:判断assert后面紧跟的语句是True还是False,如果是True则继续执行print,如果是False则中断程序,调用默认的异常处理器,同时输出assert语句逗号后面的提示信息。
with语句:如果with语句或语句块中发生异常,会调用默认的异常处理器处理,但文件还是会正常关闭。
-
copy()与deepcopy()的区别
copy是浅拷贝,只拷贝可变对象的父级元素。deepcopy是深拷贝,递归拷贝可变对象的所有元素。
-
函数装饰器有什么作用(常考)
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
-
简述Python的作用域以及Python搜索变量的顺序
Python作用域简单说就是一个变量的命名空间。代码中变量被赋值的位置,就决定了哪些范围的对象可以访问这个变量,这个范围就是变量的作用域。在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域。Python的变量名解析机制也称为LEGB法则:本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosinglocals)→全局/模块作用域(Global)→内置作用域(Built-in)
-
新式类和旧式类的区别,如何确保使用的类是新式类
为了统一类(class)和类型(type),python在2.2版本引进来新式类。在2.1版本中,类和类型是不同的。
为了确保使用的是新式类,有以下方法:
放在类模块代码的最前面__metaclass__= type
从内建类object直接或者间接地继承
在python3版本中,默认所有的类都是新式类。 -
简述__new__和__init__的区别
创建一个新实例时调用__new__,初始化一个实例时用__init__,这是它们最本质的区别。
new方法会返回所构造的对象,init则不会.
new函数必须以cls作为第一个参数,而init则以self作为其第一个参数.
-
Python垃圾回收机制(常考)
PythonGC主要使用引用计数(referencecounting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(markand sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generationcollection)以空间换时间的方法提高垃圾回收效率。
1引用计数
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。
优点:
简单实时性 缺点:
维护引用计数消耗资源循环引用
2标记-清除机制
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
3分代技术
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
-
Python中的@property有什么作用?如何实现成员变量的只读属性?
@property装饰器就是负责把一个方法变成属性调用,通常用在属性的get方法和set方法,通过设置@property可以实现实例成员变量的直接访问,又保留了参数的检查。另外通过设置get方法而不定义set方法可以实现成员变量的只读属性。
-
*argsand **kwargs
*args代表位置参数,它会接收任意多个参数并把这些参数作为元组传递给函数。**kwargs代表的关键字参数,允许你使用没有事先定义的参数名,另外,位置参数一定要放在关键字参数的前面。
-
有用过withstatement吗?它的好处是什么?具体如何实现?
with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
-
whatwill be the output of the code below? explain your answer
def
extend_list
(val, list=[]):
list.append(val)
return
list
list1 = extend_list(
10
)
list2 = extend_list(
123
, [])
list3 = extend_list(
'a'
)
print(list1)
# list1 = [10, 'a']
print(list2)
# list2 = [123, []]
print(list3)
# list3 = [10, 'a']
class
Parent
(object):
x =
1
class
Child1
(Parent):
pass
class
Child2
(Parent):
pass
print(Parent.x, Child1.x, Child2.x)
# [1,1,1]
Child1.x =
2
print(Parent.x, Child1.x, Child2.x)
# [1,2,1]
Partent.x =
3
print(Parent.x, Child1.x, Child2.x)
# [3,2,3]
-
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
arr
= [[
1
,
4
,
7
,
10
,
15
], [
2
,
5
,
8
,
12
,
19
], [
3
,
6
,
9
,
16
,
22
], [
10
,
13
,
14
,
17
,
24
], [
18
,
21
,
23
,
26
,
30
]]
def
getNum(num,
data
=
None
):
while
data
:
if
num >
data
[0][-1]:
del
data
[0]
print(
data
)
getNum(num,
data
=
None
)
elif num <
data
[0][-1]:
data
= list(
zip
(*
data
))
del
data
[-1]
data
= list(
zip
(*
data
))
print(
data
)
getNum(num,
data
=
None
)
else
:
return
True
data
.clear()
return
False
if
__name__ == '__main__':
print(getNum(
18
, arr))
-
获取最大公约数、最小公倍数
a =
36
b =
21
def
maxCommon
(a, b):
while
b: a,b = b, a%b
return
a
def
minCommon
(a, b):
c = a*b
while
b: a,b = b, a%b
return
c//a
if
__name__ ==
'__main__'
:
print(maxCommon(a,b))
print(minCommon(a,b))
-
获取中位数
def
median
(data):
data.sort()
half = len(data) //
2
return
(data[half] + data[~half])/
2
l = [
1
,
3
,
4
,
53
,
2
,
46
,
8
,
42
,
82
]
if
__name__ ==
'__main__'
:
print(median(l))
-
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
def
getOneCount
(num):
if
num >
0
:
count = b_num.count(
'1'
)
print(b_num)
return
count
elif
num <
0
:
b_num = bin(~num)
count =
8
- b_num.count(
'1'
)
return
count
else
:
return
8
if
__name__ ==
'__main__'
:
print(getOneCount(
5
))
print(getOneCount(
-5
))
print(getOneCount(
0
))
以上就是我面试过程中所被问到的问题,算法题还是比较少的,也只有2家公司要求写算法,数据结构似乎被问到的不是特别多,就问到了一个B+树的结构。数据库问到的是索引相关的优化。稍微有些基础的都能回答上来,但是最好可以深层次的探讨一下。
本文只做抛砖引玉之用,有些见解还不是特别成熟,希望可以为学习Python找工作的伙伴们提供一些帮助,面试过程当中最重要的一点是放平心态,求职过程是双方的,不需要太过紧张,把自己掌握的知识充分表达出来就好。只要你是匹千里马,迟早会被伯乐牵出来遛一遛的。