第七章 移动端数据的爬取

  • 基于某一款抓包工具 : fiddler ,青花瓷 ,miteproxy

7.1 fiddler 基本配置

7.1.1fiddler简介和安装

  • 什么是Fiddler?

    • Fiddler是位于客户端和服务器端的HTTP代理,也是目前最常用的http抓包工具之一 。 它能够记录客户端和服务器之间的所有 HTTP请求,可以针对特定的HTTP请求,分析请求数据、设置断点、调试web应用、修改请求的数据,甚至可以修改服务器返回的数据,功能非常强大,是web调试的利器。

  • Fiddler安装

    • Fiddler下载地址:https://www.telerik.com/fiddler

    • 傻瓜式安装,一键到底。Fiddler软件界面如图所示:

7.1.2手机APP抓包设置

  • Fiddler设置打开Fiddler软件,打开工具的设置。(Fiddler软件菜单栏:Tools->Options)在HTTPS中设置如下:

  • 在Connections中设置如下

    • 这里使用默认8888端口,当然也可以自己更改,但是注意不要与已经使用的端口冲突:Allow remote computers to connect:允许别的机器把请求发送到fiddler上来

  • 安全证书下载

    • 在电脑浏览器中输入地址:http://localhost:8888/,点击FiddlerRoot certificate,下载安全证书:

  • 安全证书安装

    • 证书是需要在手机上进行安装的,这样在电脑Fiddler软件抓包的时候,手机使用电脑的网卡上网才不会报错。

  • Android手机安装:

    • 把证书放入手机的内置或外置存储卡上,然后通过手机的"系统安全-》从存储设备安装"菜单安装证书。然后找到拷贝的FiddlerRoot.cer进行安装即可。安装好之后,可以在信任的凭证中找到我们已经安装好的安全证书。

  • 苹果手机安装:

    • 保证手机网络和fiddler所在机器网络是同一个网段下的在safari中访问http://fiddle机器ip:fiddler端口,进行证书下载。然后进行安装证书操作。在手机中的设置-》通用-》关于本机-》证书信任设置-》开启fiddler证书信任

  • 局域网设置 想要使用Fiddler进行手机抓包,首先要确保手机和电脑的网络在一个内网中,可以使用让电脑和手机都连接同一个路由器。当然,也可以让电脑开放WIFI热点,手机连入。这里,我使用的方法是,让手机和电脑同时连入一个路由器中。最后,让手机使用电脑的代理IP进行上网。 在手机上,点击连接的WIFI进行网络修改,添加代理。进行手动设置,ip和端口号都是fiddler机器的ip和fiddler上设置的端口号。

  • Fiddler手机抓包测试

    • 上述步骤都设置完成之后,用手机浏览器打开百度首页,我们就可以顺利抓包了

7.1.3移动端数据的捕获流程

  • tools --> options --> connection -->allow remote

  • http: fiddler所在pc机的ip :8888/ 访问到一张提供了证书下载功能的界面

  • fiddler所在机器和手机在同一网段下 :在手机上浏览器中访问 http: fiddler所在pc机的ip :8888 获取子页面进行证书的下载和安装(证书信任的操作)

  • 配置你手机的代理 :将手机的代理配置成 fiddler所对应的pc机的ip和手机自己的端口

  • 就可以让fiddler捕获手机发起的http和https的请求

7.2 scrapy ,pyspider

#总结:​#爬虫文件中的属性和方法 name :爬虫文件唯一标识 start_url:该列表中的url会被自动的进行请求发送#自动请求发送的过程: def start_requests(self):        for url in self.start_urls:            yield scrapy.Request(url,callback=self.parse)​#数据解析: scrapy中封装的xpath进行数据解析 

#scrapy中的xpath 和 etree中的xpath的区别 scrapy的xpath进行数据解析后返回的列表元素为Selector对象,extract或extract_first这两个方法将Selector对象中对应的数据取出​

7.2.1Scrapy简介

  • 什么是框架?如何学习

    • 框架就是一个集成各种功能且具有很强通用性(可以被应用在各种不同的需求中)的一个项目模板

    • 我们只需要学习框架中封装好的相关功能即可

  • scrapy 集成的功能

    • 高性能的数据解析操作 ,持久化存储,高性能的数据下载操作......

  • 环境的安装(windows)

    • pip install wheel

    • 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

    • 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl

    • pip3 install pywin32

    • pip3 install scrapy

7.2.2 scrapy的基本使用

  • 创建一个工程 :scrapy startproject firstBlood

    • 项目结构:

      project_name/   scrapy.cfg:            #项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)   project_name/       __init__.py         items.py            #设置数据存储模板,用于结构化数据,如:Django的Model       pipelines.py        #数据持久化处理       settings.py         #配置文件,如:递归的层数、并发数,延迟下载等       spiders/            #爬虫目录,如:创建文件,编写爬虫解析规则           __init__.py
  • 创建爬虫应用程序:(必须在spider这个目录下创建一个爬虫文件)

    • cd proName

    • scrapy genspider spiderName www.xx.com

  • 编写爬虫文件:在步骤2执行完毕后,会在项目的spiders中生成一个应用名的py爬虫文件

    import scrapyclass FirstSpider(scrapy.Spider):#爬虫文件的名称:爬虫文件的唯一标识(在spiders子目录下是可以创建多个爬虫文件)name = 'first'#允许的域名# allowed_domains = ['www.baidu.com']#起始的url列表:列表中存放的url会被scrapy自动的进行请求发送start_urls = ['https://www.baidu.com/','https://www.sogou.com/']#用作于数据解析:将start_urls列表中对应的url请求成功后的响应数据进行解析def parse(self, response):print(response.text) #获取字符串类型的响应内容print(response.body)#获取字节类型的相应内容
    
  • 设置修改settings.py配置文件相关配置

    # settings.py 文件中#不遵从robots协议#进行UA伪装#进行日志等级设定: LOG_LEVEL = False
    

    setting.py中 ----- 基于终端指令的持久化存储操作

    BOT_NAME = 'firstBlood'SPIDER_MODULES = ['firstBlood.spiders']
    NEWSPIDER_MODULE = 'firstBlood.spiders'# Crawl responsibly by identifying yourself (and your website) on the user-agent
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'       #进行ua伪装
    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False    #不遵从robotstx协议
    LOG_LEVEL = 'ERROR'       #输出错误类型的日志
    

    setting.py ------------- 基于管道的持久化存储

    BOT_NAME = 'qiubaiPro'
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'SPIDER_MODULES = ['qiubaiPro.spiders']
    NEWSPIDER_MODULE = 'qiubaiPro.spiders'ITEM_PIPELINES = {'qiubaiPro.pipelines.QiubaiproPipeline': 300, #300表示的是优先级(数值越小优先级越大)
    }
    
  • 执行爬虫程序:scripy crawl spiderName

    • 不输出日志(错误信息会在日志中输出,不要使用) scripy crawl spiderName --nolog

7.2.3scrapy框架持久化存储

#持久化存储#基于终端指令特性 : 只能将 parse 方法的返回值存储到本地的磁盘文件中指令 : scripy crawl spiderName -o filepath#基于管道#实现流程1.数据解析2.在item类中定义相关属性3.将解析的数据封装到一个 item 对象中(item文件中对应类的对象)4.向管道提交item5.在管道文件中的 process_item 方法中接收 item 进行持久化存储6.在配置文件中开启管道#管道中需注意细节:1.配置文件中开启的管道是一个字典,字典中的键值表示的就是某一个管道2.在管道对应的源文件中其实可以定义多个管道类,一种形式的持久化存储3.在process_item方法中的 return item 表示的是提交给下一个即将被执行的管道类4.爬虫文件中yield item 只可以将item传递给第一个被执行的(优先级最高的)管道#将同一份数据持久化存储到不同平台中:#分析1.管道文件中的一个管道内负责数据的一种形式的持久化存储

setting.py中 ----- 基于终端指令的持久化存储操作

BOT_NAME = 'firstBlood'SPIDER_MODULES = ['firstBlood.spiders']
NEWSPIDER_MODULE = 'firstBlood.spiders'# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'       #进行ua伪装
# Obey robots.txt rules
ROBOTSTXT_OBEY = False    #不遵从robotstx协议
LOG_LEVEL = 'ERROR'       #输出错误类型的日志

setting.py ------------- 基于管道的持久化存储

BOT_NAME = 'qiubaiPro'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'SPIDER_MODULES = ['qiubaiPro.spiders']
NEWSPIDER_MODULE = 'qiubaiPro.spiders'ITEM_PIPELINES = {'qiubaiPro.pipelines.QiubaiproPipeline': 300, #300表示的是优先级(数值越小优先级越大)
}

糗事百科

qiubai.py

import scrapy
from qiubaiPro.items import QiubaiproItemclass QiubaiSpider(scrapy.Spider):name = 'qiubai'# allowed_domains = ['www.xxx.com']start_urls = ['https://www.qiushibaike.com/text/']#基于终端指令的持久化存储操作# def parse(self, response):#     div_list = response.xpath('//*[@id="content-left"]/div')#     all_data = []#     for div in div_list:#         #scrapy中的xpath返回的列表的列表元素一定是Selector对象,我们最终想要的解析的#         #数据一定是存储在该对象中#         #extract()将Selector对象中data参数的值取出#         # author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()#         author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()#         #列表直接调用extract表示的是将extract作用到每一个列表元素中#         content = div.xpath('./a[1]/div/span//text()').extract()#         content = ''.join(content)#         dic = {#             'author':author,#             'content':content#         }#         all_data.append(dic)#     return all_data#基于管道的持久化存储def parse(self, response):div_list = response.xpath('//*[@id="content-left"]/div')all_data = []for div in div_list:#scrapy中的xpath返回的列表的列表元素一定是Selector对象,我们最终想要的解析的#数据一定是存储在该对象中#extract()将Selector对象中data参数的值取出# author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()#列表直接调用extract表示的是将extract作用到每一个列表元素中content = div.xpath('./a[1]/div/span//text()').extract()content = ''.join(content)#将解析的数据存储到item对象item = QiubaiproItem()item['author'] = authoritem['content'] = content#将item提交给管道yield item #item一定是提交给了优先级最高的管道类

itims.py

import scrapyclass QiubaiproItem(scrapy.Item):# define the fields for your item here like:author = scrapy.Field() #Field可以将其理解成是一个万能的数据类型content = scrapy.Field()

pipelines.py 管道文件

import pymysql
from redis import Redis
class QiubaiproPipeline(object):fp = Nonedef open_spider(self,spider):print('开始爬虫......')self.fp = open('qiushibaike.txt','w',encoding='utf-8')#使用来接收爬虫文件提交过来的item,然后将其进行任意形式的持久化存储#参数item:就是接收到的item对象#该方法每接收一个item就会调用一次def process_item(self, item, spider):author = item['author']content= item['content']self.fp.write(author+':'+content+'\n')return item #item是返回给了下一个即将被执行的管道类def close_spider(self,spider):print('结束爬虫!')self.fp.close()#负责将数据存储到mysql
class MysqlPL(object):conn = Nonecursor = Nonedef open_spider(self,spider):self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123',db='spider',charset='utf8')print(self.conn)def process_item(self,item,spider):author = item['author']content = item['content']sql = 'insert into qiubai values ("%s","%s")'%(author,content)self.cursor = self.conn.cursor()try:self.cursor.execute(sql)self.conn.commit()except Exception as e:print(e)self.conn.rollback()return itemdef close_spider(self,spider):self.cursor.close()self.conn.close()#基于redis的管道存储
class RedisPL(object):conn = Nonedef open_spider(self,spider):self.conn = Redis(host='127.0.0.1',port=6379)print(self.conn)def process_item(self,item,spider):self.conn.lpush('all_data',item)     # name  value#注意:如果将字典写入redis报错:pip install -U redis==2.10.6

7.2.4scrapy框架之递归解析和post请求

递归解析(手动请求发送)

  • 使用场景 :爬取多个页码对应的页面源码数据

  • yield scrapy.Request(url,callback)

import scrapy
from qiubaiPro.items import QiubaiproItemclass QiubaiSpider(scrapy.Spider):name = 'qiubai'# allowed_domains = ['www.xxx.com']start_urls = ['https://www.qiushibaike.com/text/']#将多个页码对应的页面数据进行爬取和解析的操作url = 'https://www.qiushibaike.com/text/page/%d/'#通用的url模板pageNum = 1#parse第一次调用表示的是用来解析第一页对应页面中的段子内容和作者def parse(self, response):div_list = response.xpath('//*[@id="content-left"]/div')all_data = []for div in div_list:# scrapy中的xpath返回的列表的列表元素一定是Selector对象,我们最终想要的解析的# 数据一定是存储在该对象中# extract()将Selector对象中data参数的值取出# author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()# 列表直接调用extract表示的是将extract作用到每一个列表元素中content = div.xpath('./a[1]/div/span//text()').extract()content = ''.join(content)# 将解析的数据存储到item对象item = QiubaiproItem()item['author'] = authoritem['content'] = content# 将item提交给管道yield item  # item一定是提交给了优先级最高的管道类if self.pageNum <= 5:self.pageNum += 1new_url = format(self.url%self.pageNum)#手动请求(get)的发送yield scrapy.Request(new_url,callback=self.parse)

post请求发送

  • 问题:在之前代码中,我们从来没有手动的对start_urls列表中存储的起始url进行过请求的发送,但是起始url的确是进行了请求的发送,那这是如何实现的呢?

    • 解答:其实是因为爬虫文件中的爬虫类继承到了Spider父类中的start_requests(self)这个方法,该方法就可以对start_urls列表中的url发起请求:

        def start_requests(self):for u in self.start_urls:yield scrapy.Request(url=u,callback=self.parse)
      
  • 【注意】该方法默认的实现,是对起始的url发起get请求,如果想发起post请求,则需要子类重写该方法。

    • 方法: 重写start_requests方法,让其发起post请求:

    def start_requests(self):#请求的urlpost_url = 'http://fanyi.baidu.com/sug'# post请求参数formdata = {'kw': 'wolf',}# 发送post请求yield scrapy.FormRequest(url=post_url, formdata=formdata, callback=self.parse)
    

scrapy五大核心组件

#引擎(Scrapy)用来处理整个系统的数据流处理, 触发事务(框架核心)
#调度器(Scheduler)用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
#下载器(Downloader)用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
#爬虫(Spiders)爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
#项目管道(Pipeline)负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
  • 工作流程

    #步骤
    1.spider中的url被封装成请求对象交给引擎(每一个url对应一个请求对象);
    2.引擎拿到请求对象之后, 将其全部交给调度器;
    3.调度器拿到所有请求对象后, 通过内部的过滤器过滤掉重复的url, 最后将去重后的所有url对应的请求对象压入到队列中, 随后调度器调度出其中一个请求对象, 并将其交给引擎;
    4.引擎将调度器调度出的请求对象交给下载器;
    5.下载器拿到该请求对象去互联网中下载数据;
    6.数据下载成功后会被封装到response中, 随后response会被交给下载器;
    7.下载器将response交给引擎;
    8.引擎将response交给spiders;
    9.spiders拿到response后调用回调方法进行数据解析, 解析成功后产生item, 随后spiders将item交给引擎;
    10.引擎将item交给管道, 管道拿到item后进行数据的持久化存储.
    

7.3基于scrapy进行图片数据的爬取

  • 示例 :校花网图片的爬取

    链接地址 :http://www.521609.com/daxuemeinv/list81.html

  • 项目的创建

    • scrapy startproject imgPro

    • cd imgPro

    • scrapy genspider img www.xxx.com

  • 如何基于scrapy进行图片的爬取

    1.在爬虫文件中只需要解析出图片地址,然后将图片地址提交给管道
    2.配置文件中添加  IMAGES_STORE = './imasLib'
    1.在管道文件中进行管道类的制定:from scrapy.pipelines.images import ImagesPipeline将管道的父类修改成 ImagesPipeline重写父类的三个方法
    
  • 代码:

    img.py

    import scrapy
    from imgPro.items import ImgproItemclass ImgSpider(scrapy.Spider):name = 'img'# allowed_domains = ['www.x.com']start_urls = ['http://www.521609.com/daxuemeinv/']url = 'http://www.521609.com/daxuemeinv/list8%d.html'pageNum = 1def parse(self, response):li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')for li in li_list:img_src = 'http://www.521609.com' + li.xpath('./a[1]/img/@src').extract_first()item = ImgproItem()item['src'] = img_srcyield itemif self.pageNum < 4:self.pageNum += 1new_url = format(self.url%self.pageNum)yield scrapy.Request(new_url,callback=self.parse)
    

    setting.py

    ##不遵从robotstx协议
    ROBOTSTXT_OBEY = False#输出错误类型的日志
    LOG_LEVEL = 'ERROR'
    # LOG_FILE = './log.txt'#图片存储地址
    IMAGES_STORE = './imasLib'ITEM_PIPELINES = {'imgPro.pipelines.ImgproPipeline': 300,
    }
    

    pipelines.py

    import scrapy
    from scrapy.pipelines.images import ImagesPipelineclass ImgproPipeline(ImagesPipeline):#对某一个媒体资源进行请求发送# item 就是接收到的spider提交的数据def get_media_requests(self, item, info):yield scrapy.Request(item['src'])#指定媒体数据存储的名称def file_path(self, request, response=None, info=None):#返回原始图片名称name = request.url.split('/')[-1]print("正在下载:",name)return name#将 item 传递给下一个即将被执行的管道类def item_completed(self, results, item, info):return item
    

    items.py

    import scrapyclass ImgproItem(scrapy.Item):# define the fields for your item here like:src = scrapy.Field()

如何提升scrapy爬取数据的效率

只需要将如下五个步骤配置在配置文件中即可:

#增加并发默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。#降低日志级别:在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’#禁止cookie:如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False#禁止重试:对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False#减少下载超时:如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s

7.4请求传参

  • 实现深度爬取:爬取多个层级对应的页面数据

  • 使用场景:爬取的数据没有在同一张页面中

  • 在手动请求的时候传递item:yield scrapy.Request(url,callback,meta={'item':item})

    • 将meta这个字典传递给callback

    • 在callback中接收meta:item = response.meta['item']

  • 代码:

    movie.py

    import scrapy
    from moviePro.items import MovieproItemclass MovieSpider(scrapy.Spider):name = 'movie'# allowed_domains = ['www.xxx.com']start_urls = ['https://www.4567tv.tv/index.php/vod/show/class/动作/id/5.html']url = 'https://www.4567tv.tv/index.php/vod/show/class/动作/id/5/page/%d.html'pageNum = 1def parse(self, response):li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')for li in li_list:title = li.xpath('./div[1]/a/@title').extract_first()detail_url = "https://www.4567tv.tv" + li.xpath('./div[1]/a/@href').extract_first()item = MovieproItem()item['title'] = title# meta参数是一个字典,该参数可以传递给callback指定的回调函数,yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})if self.pageNum < 5:self.pageNum += 1new_url = format(self.url%self.pageNum)yield scrapy.Request(new_url,callback=self.parse)def parse_detail(self,response):#接收参数 response.metaitem = response.meta['item']#简介desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()item['desc'] = descyield item
    

    setting.py

    BOT_NAME = 'moviePro'SPIDER_MODULES = ['moviePro.spiders']
    NEWSPIDER_MODULE = 'moviePro.spiders'# UA伪装
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'# Obey robots.txt rules
    ROBOTSTXT_OBEY = False
    #输出错误类型的日志
    LOG_LEVEL = 'ERROR'ITEM_PIPELINES = {'moviePro.pipelines.MovieproPipeline': 300,
    }
    

    items.py

    import scrapyclass MovieproItem(scrapy.Item):# define the fields for your item here like:title = scrapy.Field()desc = scrapy.Field()
    

    pipelines.py

    class MovieproPipeline(object):def process_item(self, item, spider):print(item)return item
    

7.5scrapy中的中间件的应用

1.请求载体身份标识(可用)

user_agent_list = ["Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 ""(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1","Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 ""(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 ""(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 ""(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 ""(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 ""(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5","Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 ""(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 ""(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 ""(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]

3.代理池(不可用)

PROXY_http = ['153.180.102.104:80','195.208.131.189:56055',
]
PROXY_https = ['120.83.49.90:9000','95.189.112.214:35508',
]

3.爬虫中间件

4.下载中间件

  • 作用 :批量拦截请求和响应

拦截请求

  • UA伪装 :将所有的请求尽可能多的设定成不同的请求载体身份标识

  • 代理操作

from scrapy import signals
import random
user_agent_list = ["Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 ""(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1","Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 ""(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 ""(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 ""(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 ""(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 ""(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5","Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 ""(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 ""(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 ""(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 ""(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]PROXY_http = ['153.180.102.104:80','195.208.131.189:56055',
]
PROXY_https = ['120.83.49.90:9000','95.189.112.214:35508',
]# 下载中间件
class MovieproDownloaderMiddleware(object):# 拦截正常的请求,参数 request 就是拦截到请求对象def process_request(self, request, spider):print("i am process_request")#实现:将拦截到的请求尽可能多的设定成不同的请求载体身份标识request.headers['USER_AGENT'] = random.choice(user_agent_list)if request.url.split(":")[0] == "http":request.meta['proxy'] = 'http://' + random.choice(PROXY_http)else:request.meta['proxy'] = 'https://' + random.choice(PROXY_https)return None# 拦截响应,参数 request 就是拦截到响应def process_response(self, request, response, spider):print("i am process_response")return response# 拦截发生异常的请求def process_exception(self, request, exception, spider):print("i am process_exception")#拦截到异常的请求,然后对其进行修正,然后重新进行请求发送#代理操作if request.url.split(":")[0] == "http":request.meta['proxy'] = 'http://' + random.choice(PROXY_http)else:request.meta['proxy'] = 'https://' + random.choice(PROXY_https)#将修正后的请求进行重新发送return request

拦截响应

  • 篡改响应数据或直接替换响应对象

  • 需求 : 爬取网易新闻 国内,国际,军事,航空,无人机这五个板块下对应的新闻标题和内容

    #分析:
    1.每一个板块对应的新闻数据是动态加载出来的# selenium在scrapy中的应用:实例化浏览器对象:卸载爬虫类的构造方法中
    

    wangyi.py

    import scrapy
    from selenium import webdriver
    from wangyiPro.items import WangyiproItemclass WangyiSpider(scrapy.Spider):name = 'wangyi'# allowed_domains = ['www.xxx.com']start_urls = ['https://news.163.com']five_model_urls = []#实例化一个浏览器对象bro = webdriver.Chrome(executable_path=r'E:\飞秋\爬虫+数据\tools\chromedriver.exe')#用来解析五个板块对应的url,然后对齐进行手动请求发送def parse(self, response):model_index = [3,4,6,7,8]li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')for index in model_index:li = li_list[index]#获取五个板块对应的urlmodel_url = li.xpath('./a/@href').extract_first()self.five_model_urls.append(model_url)#对每个板块的url进行手动请求发送yield scrapy.Request(model_url,callback=self.parse_model)#用作与解析每个板块中的新闻标题和新闻详情页的url#问题:response(不满足需求的response)没有包含每一个板块中动态加载的新闻数据def parse_model(self,response):div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div')for div in div_list:title = div.xpath('./div/div[1]/h3/a/text()').extract_first()detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()item = WangyiproItem()item['title'] = title#对详情页发起请求解析出新闻内容yield scrapy.Request(detail_url,callback=self.prase_new_detail,meta={'item':item})def prase_new_detail(self,response):item = response.meta['item']content = response.xpath('//*[@id="endText"]//text()').extract()content = ''.join(content)item['content'] = contentyield  item#最后执行def closed(self,spider):self.bro.quit()
    

    items.py

    import scrapyclass WangyiproItem(scrapy.Item):# define the fields for your item here like:title = scrapy.Field()content = scrapy.Field()
    

    middlewares.py

    from scrapy import signals
    from scrapy.http import HtmlResponse
    from time import sleepclass WangyiproDownloaderMiddleware(object):def process_request(self, request, spider):return None# spider 就是爬虫文件中爬虫类实例化的对象def process_response(self, request, response, spider):#进行所有响应对象的拦截# 1.将所有响应对象中那五个不满足需求的响应对象找出#每个响应对象对应唯一一个请求对象#如果我们定位到五个响应对象的请求对象后,就可以通过该请求对象定位到指定的响应对象#可以通过五个板块的 url 定位请求对象#总结: url ==> request ==> response# 2.将找出的五个不满足需求的响应对象进行修正(替换)#  spider.five_model_urls :五个板块对应的urlbro = spider.broif request.url in spider.five_model_urls:bro.get(request.url)sleep(1)#包含了动态加载的新闻数据page_text = bro.page_source#如果if条件成立,则该response就是五个板块对应的响应对象new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)return new_responsereturn responsedef process_exception(self, request, exception, spider):pass
    

    pipelines.py

    class WangyiproPipeline(object):def process_item(self, item, spider):print(item)return item
    

    setting.py

    BOT_NAME = 'wangyiPro'SPIDER_MODULES = ['wangyiPro.spiders']
    NEWSPIDER_MODULE = 'wangyiPro.spiders'# UA伪装
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'ROBOTSTXT_OBEY = False
    LOG_LEVEL = 'ERROR'DOWNLOADER_MIDDLEWARES = {'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
    }ITEM_PIPELINES = {'wangyiPro.pipelines.WangyiproPpeline': 300,
    }
    

7.6.基于CrawSpider的全站数据爬取

  • CrawSpider简介

    • CrawSpider就是爬虫类中 Spider的一个子类

    • 除了继承到Spider的特性和功能外,还派生除了其自己独有的更加强大的特性和功能。其中最显著的功能就是”LinkExtractors链接提取器“。Spider是所有爬虫的基类,其设计原则只是为了爬取start_url列表中网页,而从爬取到的网页中提取出的url进行继续的爬取工作使用CrawlSpider更合适。

  • 使用流程

    1.创建一个基于CrawSpider的爬虫文件 : scrapy startproject sunProcd sunProscrapy genspider -t crawl spiderName www.xxx.com
    2.构造链接提取器和规则解析器链接提取器作用:可根据指定的规则进行指定链接的提取提取规则:allow = '正则表达式'规则解析器作用:获取链接提取器提取到的链接,然后进行请求发送,根据指定规则对请求到的页面源码数据进行数据解析follow = 'True' :将链接提取器 继续作用到 链接提取器所提取到的页码链接所对应的页面中
    

7.6.1 基于CrawSpider的基本使用

数据链接地址 :http://wz.sun0769.com/index.php/question/questionType?type=4&page=

sun.py

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule#未实现深度爬取:爬取的只是每个页面对应的数据
class SunSpider(CrawlSpider):name = 'sun'# allowed_domains = ['www.xxx.com']start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']#链接提取器  type=4&page=\d+link = LinkExtractor(allow=r'type=4&page=\d+')rules = (#实例化一个Rule(规则解析器)的对象Rule(link, callback='parse_item', follow=True),)def parse_item(self, response):tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')for tr in tr_list:title = tr.xpath('./td[2]/a[2]/@title').extract_first()status = tr.xpath('./td[3]/span/text()').extract_first()print(title,status)

setting.py

BOT_NAME = 'sunPro'SPIDER_MODULES = ['sunPro.spiders']
NEWSPIDER_MODULE = 'sunPro.spiders'# UA伪装
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'

7.6.2 基于CrawSpider的深度爬取

sun.py

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.items import SunproItem,SunproItem_detailclass SunSpider(CrawlSpider):name = 'sun'# allowed_domains = ['www.xxx.com']start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']#链接提取器link = LinkExtractor(allow=r'type=4&page=\d+')#详情页url  question/201909/426989.shtmllink_detail = LinkExtractor(allow=r'question/\d+/\d+\.shtml')rules = (#实例化一个Rule(规则解析器)的对象Rule(link, callback='parse_item', follow=True),Rule(link_detail, callback='parse_datail'),)def parse_item(self, response):tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')for tr in tr_list:title = tr.xpath('./td[2]/a[2]/@title').extract_first()status = tr.xpath('./td[3]/span/text()').extract_first()num = tr.xpath('./td[1]/text()').extract_first()item = SunproItem()item['title'] = titleitem['status'] = statusitem['num'] = numif num:yield itemdef parse_datail(self,response):content = response.xpath('/html/body/div[9]/table[2]/tbody/tr[1]//text()').extract()content = ''.join(content)num = response.xpath('/html/body/div[9]/table[1]/tbody/tr/td[2]/span[2]/text()').extract_first()if num:num = num.split(':')[-1]item = SunproItem_detail()item['content'] = contentitem['num'] = numyield item

pipelines.py

class SunproPipeline(object):def process_item(self, item, spider):if item.__class__.__name__ == "SunproItem_detail":content = item['content']num = item['num']print(content,num)else:title = item['title']status = item['status']num = item['num']print(title,status,num)

items.py

class SunproItem(scrapy.Item):title = scrapy.Field()status = scrapy.Field()num = scrapy.Field()class SunproItem_detail(scrapy.Item):content = scrapy.Field()num = scrapy.Field()

7.7.分布式

7.7.1基本概念

  • 什么是分布式爬虫?

    • 基于多台电脑组建一个分布式机群,然后让机群中的每一台电脑执行同一组程序,然后让它们对同一个网站的数据进行分布爬取

  • 为什么要用分布式爬虫?

    • 提升爬取数据的效率

  • 如何实现分布式爬虫?

    • 基于scrapy + redis 的形式实现分布式

      • scrapy 结合着 scrapy-redis组件实现分布式

  • 原生的scrapy无法实现分布式原因?

    • 调度器无法被分布式群共享

    • 管道无法被共享

  • scrapy-redis组件的作用

    • 提供可以被共享的调度器和管道

  • 环境安装:

    • pip insatll redis

    • pip install scrapy-redis

7.7.2分布式流程

#1.创建一个基于CrawSpider的爬虫文件 : scrapy startproject sunProcd sunProscrapy genspider -t crawl spiderName www.xxx.com
#2.修改当前的爬虫文件1.导包 : from scrapy_redis.spiders import RedisCrawlSpider2.将当前爬虫类的父类修改成RedisCrawlSpider3.将start_urls替换成redis_key = 'xxx'  #表示的是可被共享调度器中队列的名称4.编写爬虫类爬取数据的操作
#3.对setting进行操作 #指定管道开启可被共享的管道 :ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400}#指定可被共享的调度器# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True  #指定redis服务REDIS_HOST = 'redis服务的ip地址'REDIS_PORT = 6379#4.对redis配置文件进行配置 (redis.windows.conf)56行 :  #bind 127.0.0.175行 :  protected-mode yes  --> protected-mode no
#5.携带配置文件启动redis服务   redis-server .\redis.windows.conf地址: E:\飞秋\爬虫+数据\tools\redis\Redis-x64-3.2.100
#6.启动redis客户端 :  redis-cli
#7.执行当前的工程进入到爬虫文件对应的目录中:scrapy runspider xxx.py
#8.向调度器队列中仍入一个起始的url:队列在哪里呢?答:队列在redis中        lpush fbsQueue www.xxx.com
  • 代码

    fbs.py

    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    from scrapy_redis.spiders import RedisCrawlSpider
    from fbsPro.items import FbsproItemclass FbsSpider(RedisCrawlSpider):name = 'fbs'# allowed_domains = ['www.xxx.com']# start_urls = ['http://www.xxx.com/']# redis_key表示的是可被共享调度器中队列的名称redis_key = 'fbsQueue'rules = (Rule(LinkExtractor(allow=r'type=4&page=\d+'), callback='parse_item', follow=True),)def parse_item(self, response):tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')for tr in tr_list:title = tr.xpath('./td[2]/a[2]/@title').extract_first()status = tr.xpath('./td[3]/span/text()').extract_first()item = FbsproItem()item['title'] = titleitem['status'] = statusyield item
    

    items.py

    import scrapyclass FbsproItem(scrapy.Item):title = scrapy.Field()status = scrapy.Field()
    

    settings.py

    BOT_NAME = 'fbsPro'SPIDER_MODULES = ['fbsPro.spiders']
    NEWSPIDER_MODULE = 'fbsPro.spiders'ROBOTSTXT_OBEY = True#开启可被共享的管道
    ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400
    }#指定可被共享的调度器
    # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    # 使用scrapy-redis组件自己的调度器
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
    SCHEDULER_PERSIST = True#指定redis服务
    REDIS_HOST = '192.168.11.50'
    REDIS_PORT = 6379
    

7.8 增量式爬虫

转载于:https://www.cnblogs.com/lilinyuan5474/p/11498017.html

python爬虫入门 之 移动端数据的爬取相关推荐

  1. 手机python爬虫教程_python爬虫入门 之 移动端数据的爬取

    第七章 移动端数据的爬取 基于某一款抓包工具 : fiddler ,青花瓷 ,miteproxy 7.1 fiddler 基本配置 7.1.1fiddler简介和安装 什么是Fiddler? Fidd ...

  2. Python爬虫之豆瓣电影评论数据的爬取(十四)

    原创不易,转载前请注明博主的链接地址:Blessy_Zhu https://blog.csdn.net/weixin_42555080 本次代码的环境: 运行平台: Windows Python版本: ...

  3. python爬虫教程下载-Python爬虫入门教程!手把手教会你爬取网页数据

    其实在当今社会,网络上充斥着大量有用的数据,我们只需要耐心的观察,再加上一些技术手段,就可以获取到大量的有价值数据.这里的"技术手段"就是网络爬虫.今天就给大家分享一篇爬虫基础知识 ...

  4. Python爬虫入门实例九之Scrapy框架爬取股票信息(亲测有效)

    文章目录 写在前面 一.准备工作 二.具体步骤 1.建立工程和Spider模板 2.编写Spider 3.编写pipelines 4.更改settings 5.运行爬虫 三.注意事项 1.文件内容为空 ...

  5. python爬虫入门教程04:招聘信息爬取

    前言

  6. Python爬虫入门教程10:彼岸壁纸爬取

    基本开发环境

  7. Python爬虫:运用多线程、IP代理模块爬取百度图片上小姐姐的图片

    Python爬虫:运用多线程.IP代理模块爬取百度图片上小姐姐的图片 1.爬取输入类型的图片数量(用于给用户提示) 使用过百度图片的读者会发现,在搜索栏上输入关键词之后,会显示出搜索的结果,小编想大多 ...

  8. python爬虫--Scrapy框架--Scrapy+selenium实现动态爬取

    python爬虫–Scrapy框架–Scrapy+selenium实现动态爬取 前言 本文基于数据分析竞赛爬虫阶段,对使用scrapy + selenium进行政策文本爬虫进行记录.用于个人爬虫学习记 ...

  9. Python爬虫之scrapy框架360全网图片爬取

    Python爬虫之scrapy框架360全网图片爬取 在这里先祝贺大家程序员节快乐,在此我也有一个好消息送给大家,本人已开通了微信公众号,我会把资源放在公众号上,还请大家小手动一动,关注过微信公众号, ...

最新文章

  1. Android 开源框架Universal-Image-Loader学习
  2. ui设计师常用的设计工具_2020年应该使用哪个UI设计工具?
  3. sqlalchemy外键和relationship查询
  4. 学会这5种JS函数继承方式,前端面试你至少成功50%
  5. Python 之 异常检测/异常点搜索
  6. PLTS中计算Skew(计算延时差:对内/对间)
  7. android mp4 画面裁剪,说说Android的视频裁剪(三)
  8. Debian 7 安装metasploit
  9. mfc 定时器绘制旋转六边形_OpenGL——旋转的六边形(动画)
  10. 500以内降噪蓝牙耳机推荐,2023年热门降噪蓝牙耳机推荐
  11. 手机修改设备信息之安装xposed框架
  12. ElasticStack搭建Java日志收集分析,并将其构建docker镜像-学习笔记
  13. vyos-vyatta在ospf和bgp之间路由重发布使用 打tag形式进行路由过滤
  14. 大数据与认识论一、主体与对象
  15. 【Linux】设备和磁盘分区|/dev/sda
  16. 非标自动化机械设计行业未来发展趋势
  17. pandas、pyecharts绘制基金走势图-01
  18. java接口废弃注释_Spring Boot如何让Web API自动生成文档,并解决swagger-annotations的API注解description属性废弃的问题...
  19. 啊哈哈哈哈 C#按日期生成文件夹,并在文件夹中写入文件
  20. 【陌陌】数据分析师四面

热门文章

  1. c语言中如何输入log函数,在C语言中使用对数函数的方法
  2. android 执行病毒,警惕安卓手机病毒Android/Fakeapp.ox
  3. 我的创作纪念日——“永远相信美好的事情即将发生”
  4. 高等代数 矩阵的运算(第4章)1 加法,数量乘法,矩阵乘法,一些特殊矩阵
  5. docker国内镜像拉取和镜像加速registry-mirrors配置修改
  6. Elasticsearch7.3文档管理
  7. 开源在线客服系统源码(PHP开发的网页在线客服聊天系统源码)
  8. 一元二次方程虚根求法java_java怎么求一元二次方程虚根,虚根i怎么定义啊。
  9. 附件上传在IE中的问题
  10. BUUCTF 面具下的flag writeup