"

晨光彩票app-APP全能版下载拥有全球最顶尖的原生APP,每天为您提供千场精彩体育赛事,晨光彩票app-APP全能版下载更有真人、彩票、电子老虎机、真人电子竞技游戏等多种娱乐方式选择,晨光彩票app-APP全能版下载让您尽享娱乐、赛事投注等,且无后顾之忧!

"
首頁 > 【物聯網識別】cve_details漏洞爬蟲 && 設備指紋庫爬蟲

【物聯網識別】cve_details漏洞爬蟲 && 設備指紋庫爬蟲

互聯網 2021-03-04 14:10:57

設計要求 設計并制作出一種漏洞掃描平臺,其要求如下:

( 1 )熟悉爬蟲,爬取漏洞、設備詳細數據,構建漏洞庫、設備指紋庫。

( 2 )使用 nmap 工具掃描網絡,使用 zgrab2 工具輔助掃描網絡。

( 3 )完成設備指紋識別,漏洞匹配過程。

( 4 )使用 nessus 工具驗證漏洞。

( 5 )搭建可視化平臺。

( 6 )完成設計報告

爬蟲:

本項目中使用 python 語言對 CVE_Details、CNVD、 CNNVD、securityfocus、 ics_cnvd 等漏洞網站爬取不少于 100000 條漏洞詳細信息,爬取“CVE 編號”、 “危害等級”、“漏洞類型”、“供應商”、“型號”、“設備類型”、“固件版本號”等 信息,構建 CVE 漏洞-設備信息映射庫。

同時,使用 python 語言對京東、亞馬遜、淘寶等電商網站爬取不少于 100000 條設備詳細信息,爬取“設備類型”、“設備品牌”、“設備型號”等信息,并將其 構建一個設備指紋庫。

本節小目錄 (本文為作者踩坑記錄,先看別直接上手,代碼在最后)

scrapy框架介紹

CVE_Details爬蟲代碼

CVE漏洞信息數據庫

setting設置

設備信息爬蟲(蘇寧)

設備信息爬蟲(京東)

我只要代碼?。ê绵?,哥)

scrapy框架

Scrapy是一個快速的高級Web爬網和Web爬網框架,用于對網站進行爬網并從其頁面中提取結構化數據。

在這里插入圖片描述

Scrapy Engine(引擎): 負責Spider、ItemPipeline、 Downloader、Scheduler中間的通訊, 信號、數據傳遞等, Scheduler(調度器): 負責接受引擎發送過來的Request請求,并按照一定的方式進行整理排列, 入隊,當引擎需要時,交還給引擎, Downloader (下載器): 負責下載Scrapy Engine(引擎發送的所有Requests請求,并將其獲取到的Responses交還給Scrapy Engine(引擎),由引擎交給Spider來處理, Spider (爬蟲) : 它負責處理所有Responses,從中分析提取數據,獲取ltem字段需要的數據,并將需要跟進的URL提交給引擎,再次進入Scheduler(調度器), Item Pipeline(管道): 負責處理Spider中獲取到的Item,并進行進行后期處理(詳細分析、過濾存儲等)的地方. Downloader Middlewares (下載中間件) : 你可以當作是一個可以自定義擴展下載功能的組件。 Spider Middlewares (Spider中間件) : 你可以理解為是一個可以自定擴展和操作引擎和Spider中間通信的功能組件(比如進入Spider的Responses;和從Spider出去的Requests)

運作流程:

1、從spider中獲取到初始url給引擎,告訴引擎幫我給調度器;

2、引擎將初始url給調度器,調度器安排入隊列;

3、調度器告訴引擎已經安排好,并把url給引擎,告訴引擎,給下載器進行下載;

4、引擎將url給下載器,下載器下載頁面源碼;

5、下載器告訴引擎已經下載好了,并把頁面源碼response給到引擎;

6、引擎拿著response給到spider,spider解析數據、提取數據;

7、spider將提取到的數據給到引擎,告訴引擎,幫我把新的url給到調度器入隊列,把信息給到Item Pipelines進行保存;

8、Item Pipelines將提取到的數據保存,保存好后告訴引擎,可以進行下一個url的提取了;

9、循環3-8步,直到調度器中沒有url,關閉網站(若url下載失敗了,會返回重新下載)。

安裝scrapy

直接用anaconda或者pip命令安裝,推薦裝入venv虛擬環境

conda install scrapy 或 pip install scrapy

可以在命令行直接輸入scrapy驗證是否安裝成功

在這里插入圖片描述

創建項目

scrapy startproject 項目名

就會創建以下目錄結構的項目文件夾

tutorial/scrapy.cfg# deploy configuration filetutorial/ # project's Python module, you'll import your code from here__init__.pyitems.py# project items definition filemiddlewares.py# project middlewares filepipelines.py# project pipelines filesettings.py # project settings filespiders/# a directory where you'll later put your spiders__init__.py

在這里插入圖片描述

這些文件分別是:

scrapy.cfg: 項目的配置文件,現在可以先忽略。

tutorial/: 該項目的python模塊。

tutorial/items.py: 項目中的item文件。

? Item 是保存爬取到的數據的容器;其使用方法和python字典類似, 并且提供了額外保護機制來避免拼寫錯誤導致的未定義字段錯誤。

tutorial/pipelines.py: 項目中的pipelines文件。

Scrapy提供了pipeline模塊來執行保存數據的操作。在創建的 Scrapy 項目中自動創建了一個 pipeline.py 文件,同時創建了一個默認的 Pipeline 類。比如我們可以在里面寫把item提取的數據保存到mysql數據庫的方法。

tutorial/settings.py: 項目的設置文件。

settings.py是Scrapy中比較重要的配置文件,里面可以設置的內容非常之多。

tutorial/spiders/: 放置spider代碼的目錄。爬蟲文件就放在里面

創建爬蟲

scrapy genspider 爬蟲名 要爬取的網站域名 # 注意爬蟲名不要和項目名沖突,網站域名指一級域名,如:baidu.com

生成的爬蟲文件會放在文件夾項目名/spider/下為爬蟲名.py

啟動爬蟲

scrapy crawl 爬蟲名字# 注意是名字,不是爬蟲py文件 CVE_Details爬蟲

先從setting講起還是從網頁講起呢

不說話那就網頁吧!

訪問https://www.cvedetails.com可以進入CVE_Details的主頁面,這就是我們的重要漏洞庫來源,看左邊Browse–>vulnerabilities by data 點進去會發現自有記錄以來所有的漏洞數量信息,按照年->月->頁數來爬取太麻煩,跳過月,直接按照每年的爬取

隨便點進一個年份,發現url為https://www.cvedetails.com/vulnerability-list/year-1999/vulnerabilities.html感覺可以替換里面的年份,試試果然可以成功訪問,由于每年漏洞數量和頁數不一樣,頁數的最大值不好搞,,直接選擇前面這個簡單的漏洞數量,然后進行除以50的向上取整運算,就能得到頁數

在這里插入圖片描述

再觀察觀察每一頁的url,發現第2頁開始url就變復雜了

https://www.cvedetails.com/vulnerability-list.php?vendor_id=0&product_id=0&version_id=0&page=3&hasexp=0&opdos=0&opec=0&opov=0&opcsrf=0&opgpriv=0&opsqli=0&opxss=0&opdirt=0&opmemc=0&ophttprs=0&opbyp=0&opfileinc=0&opginf=0&cvssscoremin=0&cvssscoremax=0&year=1999&month=0&cweid=0&order=1&trc=894&sha=8fdcb89732c98600636042e1eff8c1b2ff5cb25d

嘗試過后發現幾個重要的參數,page(頁數), year(年份), trc(這個就是數量,去掉也沒事)

問題解決,通過 year構造的url --> 得到頁數并構造頁數的url --> 得到每一條cve鏈接 -->訪問cve頁面爬取信息返回管道

最重要的是xpath選擇器,推薦看官方文檔的介紹,然后推薦一個很方便嘗試的方式,比如第一步,我們訪問用year構造的url,然后用選擇器得到頁數,可以像下面一樣在cmd中輸入

scrapy shell https://www.cvedetails.com/vulnerability-list/year-1999/vulnerabilities.html

可以很方便的進行選擇器的調試,我通常在這里試驗選擇器效果

在這里插入圖片描述

回到這個網頁,只需要獲取這個div下的b標簽

在這里插入圖片描述 在這里插入圖片描述

在cmd里試驗

>>> response.selector.xpath('//div[@class="paging"]')[, ] >>> response.selector.xpath('//div[@class="paging"]').get() '' >>> response.selector.xpath('//div[@class="paging"]/b').get()'894' >>> response.selector.xpath('//div[@class="paging"]/b/text()').get() '894'

輕松到手!于是可以開始寫了,在生成的py文件里應該有默認代碼,稍微改改就能自己用

class CveDetailSpider(scrapy.Spider):name = 'cve_detail'allowed_domains = ['https://www.cvedetails.com']start_urls = ["https://www.cvedetails.com/vulnerability-list/year-" + str(i) + "/vulnerabilities.html" for i in range(1999, 2021)]# 建議大家這里改為range(2020, 1998, -1)倒序爬取def parse(self, response):# 得到頁數,生成url# 獲取cve的數量nums = response.selector.xpath('//div[@id="pagingb"]/b/text()').get()# 向上取整算出頁數pages = ceil(int(nums)/50)# 遍歷年份1999-2020年for year in range(1999, 2021):# 遍歷頁數for page in range(1, pages+1): # 通過page,year,nums生成頁面的urlnewurl = self.get_url(str(page), str(year), str(nums))yield scrapy.Request(url=newurl, callback=self.parse1, dont_filter=True)

scrapy默認從start_urls[] 尋找可爬取的url,然后默認調用parse()進行訪問,注意ceil()方法需要導入math庫函數,from math import ceil

get_url()是我寫的生成url的函數,如下代碼

yield 兩句話搞定,首先它相當于return,同時它還是一個生成器

def get_url(self, page, year, trc):return "https://www.cvedetails.com/vulnerability-list.php?vendor_id=0&product_id=0&version_id=0&page={}&hasexp=0&opdos=0&opec=0&opov=0&opcsrf=0&opgpriv=0&opsqli=0&opxss=0&opdirt=0&opmemc=0&ophttprs=0&opbyp=0&opfileinc=0&opginf=0&cvssscoremin=0&cvssscoremax=0&year={}&month=0&cweid=0&order=1&trc={}&sha=ef7bb39664f094781e7b403da0e482830f5837d6".format(page, year, trc)

yield scrapy.Request()有兩個參數,url和回調函數,我寫了另外一個回調函數parse1()來處理下階段的頁面解析

繼續使用cmd進行分析(上一次的退出命令是exit())

scrapy shell https://www.cvedetails.com/vulnerability-list.php?vendor_id=0&product_id=0&version_id=0&page=3&hasexp=0&opdos=0&opec=0&opov=0&opcsrf=0&opgpriv=0&opsqli=0&opxss=0&opdirt=0&opmemc=0&ophttprs=0&opbyp=0&opfileinc=0&opginf=0&cvssscoremin=0&cvssscoremax=0&year=1999&month=0&cweid=0&order=1&trc=894&sha=8fdcb89732c98600636042e1eff8c1b2ff5cb25d

這頁面簡單,我們只需要獲取一個一個的鏈接,就在這些字的a標簽下

在這里插入圖片描述

嘗試一下,就能用response.selector.xpath('//div[@id="searchresults"]/table/tr[@class="srrowns"]/td[@nowrap]/a/@href').get()定位到了,但是get()只能匹配到一個,可以用getall()將頁面上的全部拿到手

>>> response.selector.xpath('//div[@id="searchresults"]/table/tr[@class="srrowns"]/td[@nowrap]/a/@href').getall() ['/cve/CVE-2019-1020019/', '/cve/CVE-2019-1020018/', '/cve/CVE-2019-1020017/', '/cve/CVE-2019-1020016/', '/cve/CVE-2019-1020015/', '/cve/CVE-2019-1020014/', '/cve/CVE-2019-1020013/', '/cve/CVE-2019-1020012/', '/cve/CVE-2019-1020011/', '/cve/CVE-2019-1020010/', '/cve/CVE-2019-1020009/', '/cve/CVE-2019-1020008/', '/cve/CVE-2019-1020007/', '/cve/CVE-2019-1020006/', '/cve/CVE-2019-1020005/', '/cve/CVE-2019-1020004/', '/cve/CVE-2019-1020003/', '/cve/CVE-2019-1020002/', '/cve/CVE-2019-1020001/', '/cve/CVE-2019-1010319/', '/cve/CVE-2019-1010318/', '/cve/CVE-2019-1010317/', '/cve/CVE-2019-1010316/', '/cve/CVE-2019-1010315/', '/cve/CVE-2019-1010314/', '/cve/CVE-2019-1010312/', '/cve/CVE-2019-1010311/', '/cve/CVE-2019-1010310/', '/cve/CVE-2019-1010309/', '/cve/CVE-2019-1010308/', '/cve/CVE-2019-1010307/', '/cve/CVE-2019-1010306/', '/cve/CVE-2019-1010305/', '/cve/CVE-2019-1010304/', '/cve/CVE-2019-1010302/', '/cve/CVE-2019-1010301/', '/cve/CVE-2019-1010300/', '/cve/CVE-2019-1010299/', '/cve/CVE-2019-1010298/', '/cve/CVE-2019-1010297/', '/cve/CVE-2019-1010296/', '/cve/CVE-2019-1010295/', '/cve/CVE-2019-1010294/', '/cve/CVE-2019-1010293/', '/cve/CVE-2019-1010292/', '/cve/CVE-2019-1010290/', '/cve/CVE-2019-1010287/', '/cve/CVE-2019-1010283/', '/cve/CVE-2019-1010279/', '/cve/CVE-2019-1010275/']

隨便點進一個知道下一次跳轉是"https://www.cvedetails.com"+爬取的url,所以parse1()也會寫了

def parse1(self, response):# xpath爬取url列表detailurls = response.selector.xpath('//div[@id="searchresults"]/table/tr[@class="srrowns"]/td[@nowrap]/a/@href').getall()for detailurl in detailurls:# for循環構造每個子頁面urldurl = "https://www.cvedetails.com" + detailurlyield scrapy.Request(url=durl, callback=self.parse2, dont_filter=True)

這里又用yield回調了parse2(),沒錯我就是命名天才,略略略

老規矩,cmd

scrapy shell https://www.cvedetails.com/cve/CVE-1999-1567/

找到需要爬取的目標點,CVE編號,危害等級,漏洞類型,供應商,型號,設備類型,固件版本號

在這里插入圖片描述

# CVE編號cveid = response.selector.xpath('//h1/a/text()').get()# 危害等級score = response.selector.xpath('//div[@class="cvssbox"]/text()').get()

注意有的頁面危害等級為0.0有可能是信息丟失也有可能是保密信息,反正頁面沒顯示,寫個判斷直接跳過

if score == '0.0':return None

然后是漏洞類型,這個有點麻煩,為空的時候很難定位,有字的時候直接鎖定就行了,所以我獲取了整個表格倒數第二個標簽信息,再用re正則表達式匹配標簽的信息,如果findall()匹配不到就會返回空列表,這我很喜歡,兩句代碼搞定

vulntype =re.findall(r'">(.*?)', response.selector.xpath('//table[@id="cvssscorestable"]/tr').getall()[-2])vulntype = '' if vulntype == [] else vulntype[0]

接下來的設備就比較麻煩了,因為很有可能一個漏洞對應很多個設備同時很多個版本,總之,直接把設備列表的每一行都獲取到就行了

>>> response.selector.xpath('//table[@id="vulnprodstable"]/tr').getall()[1:] ['\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t1\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tApplication\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tSeapine Software\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tTesttrack\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t Version Details&nbspVulnerabilities\t\t\t\t\t\t\t\n\t\t\t\t\t\t']

很惡心,不過沒關系,兩個規則直接匹配

rule1 = re.compile(r'(.*)')rule2 = re.compile(r'\s+(.*?)\s+')vendor,product,_ = rule1.findall(make)producttype,_,_,version,_,_,_,_ = rule2.findall(make)

emmmm,正則有問題的可以隔壁去看看正則表達式,反正我也是用一次查一次

最后實例化管道,然后每一行的設備都和cveid他們存入管道,通過管道存入數據庫,下面是parse2()代碼

def parse2(self, response):# CVE編號,危害等級,漏洞類型,供應商,型號,設備類型,固件版本號cveid = response.selector.xpath('//h1/a/text()').get()score = response.selector.xpath('//div[@class="cvssbox"]/text()').get()if score == '0.0':return Nonevulntype =re.findall(r'">(.*?)', response.selector.xpath('//table[@id="cvssscorestable"]/tr').getall()[-2])vulntype = '' if vulntype == [] else vulntype[0]makes = response.selector.xpath('//table[@id="vulnprodstable"]/tr').getall()[1:] rule1 = re.compile(r'(.*)')rule2 = re.compile(r'\s+(.*?)\s+')for make in makes:vendor,product,_ = rule1.findall(make)producttype,_,_,version,_,_,_,_ = rule2.findall(make)item = CveDetailsItem()item['cveid'],item['score'],item['vulntype'],item['vendor'],item['product'],item['producttype'],item['version'] = cveid,score,vulntype,vendor,product,producttype,versionyield item

再貼一個完整的爬蟲代碼吧

# -*- coding: utf-8 -*-import scrapyfrom math import ceilimport refrom cve_details.items import CveDetailsItemclass CveDetailSpider(scrapy.Spider):name = 'cve_detail'allowed_domains = ['https://www.cvedetails.com']start_urls = ["https://www.cvedetails.com/vulnerability-list/year-" + str(i) + "/vulnerabilities.html" for i in range(1999, 2021)]def get_url(self, page, year, trc):return "https://www.cvedetails.com/vulnerability-list.php?vendor_id=0&product_id=0&version_id=0&page={}&hasexp=0&opdos=0&opec=0&opov=0&opcsrf=0&opgpriv=0&opsqli=0&opxss=0&opdirt=0&opmemc=0&ophttprs=0&opbyp=0&opfileinc=0&opginf=0&cvssscoremin=0&cvssscoremax=0&year={}&month=0&cweid=0&order=1&trc={}&sha=ef7bb39664f094781e7b403da0e482830f5837d6".format(page, year, trc)def parse(self, response):# 得到頁數,生成urlnums = response.selector.xpath('//div[@id="pagingb"]/b/text()').get() # 獲取cve的數量pages = ceil(int(nums)/50)# 算出頁數for year in range(1999, 2021):for page in range(1, pages+1):newurl = self.get_url(str(page), str(year), str(nums))yield scrapy.Request(url=newurl, callback=self.parse1, dont_filter=True)def parse1(self, response):detailurls = response.selector.xpath('//div[@id="searchresults"]/table/tr[@class="srrowns"]/td[@nowrap]/a/@href').getall()for detailurl in detailurls:durl = "https://www.cvedetails.com" + detailurlyield scrapy.Request(url=durl, callback=self.parse2, dont_filter=True)def parse2(self, response):# CVE編號,危害等級,漏洞類型,供應商,型號,設備類型,固件版本號cveid = response.selector.xpath('//h1/a/text()').get()score = response.selector.xpath('//div[@class="cvssbox"]/text()').get()if score == '0.0':return Nonevulntype =re.findall(r'">(.*?)', response.selector.xpath('//table[@id="cvssscorestable"]/tr').getall()[-2])vulntype = '' if vulntype == [] else vulntype[0]makes = response.selector.xpath('//table[@id="vulnprodstable"]/tr').getall()[1:] rule1 = re.compile(r'(.*)')rule2 = re.compile(r'\s+(.*?)\s+')for make in makes:vendor,product,_ = rule1.findall(make)producttype,_,_,version,_,_,_,_ = rule2.findall(make)item = CveDetailsItem()item['cveid'],item['score'],item['vulntype'],item['vendor'],item['product'],item['producttype'],item['version'] = cveid,score,vulntype,vendor,product,producttype,versionyield item# print(cveid,score,vulntype,vendor,product,producttype,version) CVE漏洞信息數據庫

這部分是寫在pipelines.py里的,不過因為里面要調用items咱先在items.py聲明一下

import scrapyclass CveDetailsItem(scrapy.Item):# define the fields for your item here like:cveid = scrapy.Field()score = scrapy.Field()vulntype = scrapy.Field()vendor = scrapy.Field()product = scrapy.Field()producttype = scrapy.Field()version = scrapy.Field()

open_spider()運行蜘蛛時將自動調用此方法

close_spider()關閉蜘蛛時將自動調用此方法

process_item()每個項目管道組件都調用此方法

簡單點說,我在open_spider里寫了每次運行爬蟲都檢查數據庫,如果有這個表,就刪掉,然后重建表,process_item里就接收爬蟲發過來的信息,然后存入數據庫中,close_spider就負責在爬蟲結束的時候關門

沒啥好說的,這部分主要是mysql的安裝和navicat與數據庫的連接,建議出門左轉百度去,直接上代碼

import pymysqlclass CveDetailsPipeline:tb = 'cve_details'number = 0def open_spider(self, spider):print("開始爬蟲!")db = spider.settings.get('MYSQL_DB_NAME','cve_db')host = spider.settings.get('MYSQL_HOST','127.0.0.1')port = spider.settings.get('MYSQL_PORT', 3306)user = spider.settings.get('MYSQL_USER','root')passwd = spider.settings.get('MYSQL_PASSWORD','root')self.db_conn =pymysql.connect(host=host, port=port, db=db, user=user, passwd=passwd, charset='utf8')self.db_cur = self.db_conn.cursor()self.db_cur.execute("DROP TABLE IF EXISTS %s"%self.tb)sql = """CREATE TABLE IF NOT EXISTS %s (id int PRIMARY KEY AUTO_INCREMENT, cveid varchar(32) NOT NULL,score varchar(16),vulntype varchar(100),vendor varchar(56),product varchar(56),producttype varchar(32),version varchar(32)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"""self.db_cur.execute(sql%self.tb)print('建表完成!')def process_item(self, item, spider):if item != None:values = (pymysql.escape_string(item['cveid']), # CVE編號pymysql.escape_string(item['score']), # 危害等級pymysql.escape_string(item['vulntype']),# 漏洞類型pymysql.escape_string(item['vendor']),# 供應商pymysql.escape_string(item['product']), # 型號pymysql.escape_string(item['producttype']), # 設備類型pymysql.escape_string(item['version'])# 固件版本號)# print(type(item['cveid']),type(item['score']),type(item['vulntype']),type(item['vendor']),type(item['product']),type(item['producttype']),type(item['version']))sql = '''INSERT INTO cve_details(cveid,score,vulntype,vendor,product,producttype,version) VALUES(%s,%s,%s,%s,%s,%s,%s)'''self.db_cur.execute(sql, values)self.number += 1if self.number >= 200:self.db_conn.commit()self.number = 0return itemdef close_spider(self, spider):print("結束爬蟲!")self.db_conn.commit()self.db_conn.close()

效果如下

在這里插入圖片描述

setting

這里有很多可以自定義的東西,默認都是被注釋的

BOT_NAME = 'demo1’ Scrapy項目的名字

SPIDER_MODULES = ['demo1.spiders'] Scrapy搜索spider的模塊列表

NEWSPIDER_MODULE = 'demo1.spiders' 使用 genspider 命令創建新spider的模塊。

ROBOTSTXT_OBEY = False是否遵守機器人協議,遵守會有很多限制爬取的文件

CONCURRENT_REQUESTS = 32并發線程數,默認16,然后我改的1000感覺沒啥效果,果然python的多線程都是假的,

DOWNLOAD_DELAY = 2爬取延時,一般1就行了,默認3,為了防止爬太快對服務器造成過大負擔然后被反爬(封ip)

DEFAULT_REQUEST_HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language': 'en','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36','Referer':'https://www.cvedetails.com/vulnerability-list/',}

爬蟲偽造文件頭,避免被當成爬蟲,給自己帶的面具,user-agent在f12里copy,referer就是跳轉鏈接,比如百度里訪問任何一個網頁都是通過百度跳轉,referer就是baidu.com

ITEM_PIPELINES = { 'cve_details.pipelines.CveDetailsPipeline': 300,}

這個就是管道的聲明,300是優先級,數字越低優先級越高

AUTOTHROTTLE_DEBUG = False啟用會顯示很多調節統計的信息

前面的都是被注釋了,下面這倆得自己寫上去,不然有可能跟我一樣跑了一半網絡波動連續幾十個請求失敗,然后沒有重新加入訪問隊列

RETRY_ENABLED:True 是否開啟重試

RETRY_TIMES:5 重試次數

在這里插入圖片描述

運行效果圖:

在這里插入圖片描述

還有一個更簡單的方法,還記得setting里面的ROBOTSTXT_OBEY嗎,直接訪問網站的robots.txt,里面文件夾下可以訪問的網址都挨個列好了~

在這里插入圖片描述

在這里插入圖片描述

看到這是不是以為就完了?如果這么認為就真的完了,看看數據庫…

回到cev_details數據庫,這個爬蟲我運行了一下午加一整晚,存了75w+數據還沒停止,數據庫里篩選觀察發現好多重復數據,淦,這爬蟲一直在循環爬,于是停止開始數據庫去重處理,下面一條是根據cveid,vulntype,vendor,product,version來篩選出重復項的id,并刪除這些id

mysql> delete from cve_details where id in (select id from (select max(id) as ID,cveid,vulntype,vendor,product,version,count(*) as c from cve_details group by cveid,vulntype,vendor,product,version having c>1) as t);Query OK, 3736 rows affected (3.81 sec)

每次刪除數量在3k5左右,我可是有75w+數據呢,估計55w+都是重復數據,不可能一次一次運行吧,趕緊寫了個函數,運行

CREATE DEFINER=`root`@`localhost` PROCEDURE `mydel`()BEGINwhile exists (select max(id) as ID,count(*) as c from cve_details group by cveid,vulntype,vendor,product,version having c>1)dodelete from cve_details where id in (select id from (select max(id) as ID,count(*) as c from cve_details group by cveid,vulntype,vendor,product,version having c>1) as t);end while;END

i7-7700的cpu瞬間就讓sql多占用了15%資源,磁盤讀取占用1m/s,感覺還是很慢,洗個澡去

洗澡回來發現數據庫刪完還有3k條了。。。自閉

為了從源頭上止損,我重新修改完善了爬蟲,完美解決問題,只對爬蟲文件cve_detail.py作了如下修改:

設置了訪問過目錄的記錄表goturls = set()將start_urls列表改成了start_requests()函數,這樣寫比列表的功能相比多加了一個meta參數,可以傳遞年份值,方便后續構造新的urlparse()內我新添了sha變量用于記錄頁面上獲取的認證,每一個year的sha都不一樣,如果不修改就會導致后面的翻頁功能失效,永遠爬取第一頁

下面貼完整代碼:

# -*- coding: utf-8 -*-import scrapyfrom math import ceilimport refrom cve_details.items import CveDetailsItemclass CveDetailSpider(scrapy.Spider):name = 'cve_detail'allowed_domains = ['https://www.cvedetails.com']goturls = set()def start_requests(self):for i in range(2020, 1998, -1):url = "https://www.cvedetails.com/vulnerability-list/year-" + str(i) + "/vulnerabilities.html"yield scrapy.Request(url=url, meta={'year' : i})def get_url(self, page, year, trc, sha):return "https://www.cvedetails.com/vulnerability-list.php?vendor_id=0&product_id=0&version_id=0&page={}&hasexp=0&opdos=0&opec=0&opov=0&opcsrf=0&opgpriv=0&opsqli=0&opxss=0&opdirt=0&opmemc=0&ophttprs=0&opbyp=0&opfileinc=0&opginf=0&cvssscoremin=0&cvssscoremax=0&year={}&month=0&cweid=0&order=1&trc={}&sha={}".format(page, year, trc, sha)def parse(self, response):# 得到頁數,生成urlnums = response.selector.xpath('//div[@id="pagingb"]/b/text()').get() # 獲取cve的數量pages = ceil(int(nums)/50)# 算出頁數sha = response.selector.xpath('//a[@title="Go to page 1"]/@href').get()if sha != None:sha = sha.split('=')[-1]# 獲取shaelse:return Nonefor page in range(1, pages+1):newurl = self.get_url(str(page), str(response.meta['year']), str(nums), sha)if newurl not in self.goturls:self.goturls.add(newurl)yield scrapy.Request(url=newurl, callback=self.parse1, dont_filter=True)else:print('p0訪問重復?。?!')breakdef parse1(self, response):detailurls = response.selector.xpath('//div[@id="searchresults"]/table/tr[@class="srrowns"]/td[@nowrap]/a/@href').getall()for detailurl in detailurls:durl = "https://www.cvedetails.com" + detailurlif durl not in self.goturls:self.goturls.add(durl)yield scrapy.Request(url=durl, callback=self.parse2, dont_filter=True)else:print('p1訪問重復?。?!')breakdef parse2(self, response):# CVE編號,危害等級,漏洞類型,供應商,型號,設備類型,固件版本號cveid = response.selector.xpath('//h1/a/text()').get()score = response.selector.xpath('//div[@class="cvssbox"]/text()').get()vulntype =re.findall(r'">(.*?)', response.selector.xpath('//table[@id="cvssscorestable"]/tr').getall()[-2])vulntype = '' if vulntype == [] else vulntype[0]makes = response.selector.xpath('//table[@id="vulnprodstable"]/tr').getall()[1:] rule1 = re.compile(r'(.*)')rule2 = re.compile(r'\s+(.*?)\s+')for make in makes:if 'No vulnerable product found' in make:continuevendor,product,_ = rule1.findall(make)producttype,_,_,version,_,_,_,_ = rule2.findall(make)item = CveDetailsItem()item['cveid'],item['score'],item['vulntype'],item['vendor'],item['product'],item['producttype'],item['version'] = cveid,score,vulntype,vendor,product,producttype,versionyield item

爬了21w斷電就停止了,然后使用上面的數據庫去重函數,因為在網站上每一個漏洞下的設備還有其他參數,好像叫update,連續幾條其他參數全部相同,只有這個修改了,但我們每做過濾直接放入數據庫了,所以需要去重處理

CREATE DEFINER=`root`@`localhost` PROCEDURE `mydel`()BEGINwhile exists (select max(id) as ID,count(*) as c from cve_details group by cveid,vulntype,vendor,product,version having c>1)dodelete from cve_details where id in (select id from (select max(id) as ID,count(*) as c from cve_details group by cveid,vulntype,vendor,product,version having c>1) as t);end while;END

在這里插入圖片描述

從21w條變成了16.7w條??!成功了?。。?!

在這里插入圖片描述

設備信息爬蟲(蘇寧)

爬取“設備類型”、“設備品牌”、“設備型號”等信息,并將其 構建一個設備指紋庫。

想到這我人傻了,cve漏洞庫里全是英文,淘寶京東爬下來設備類型不得全是中文嗎,比如前段時間的小米9漏洞,淘寶里設備類型就是”小米9“,設備型號也是”小米9“…這還怎么做cve和設備映射啊,這一瞬間我甚至想到了語義相似度分析,淦,想太復雜了,經過各個網站分析,淘寶不停彈窗登錄,需要寫模擬登陸,算了,京東和亞馬遜都挺好的,我因為一些原因選擇了蘇寧易購,不過幾個網站都差不多,可以類比著寫,先談談思路吧,有cve的設備幾乎都離不開“網絡設備”“智能家居”等等關鍵詞,而這只是一小部分,所以不能寫個遞歸頁面url的無腦爬蟲,可以通過關鍵詞控制范圍,這是個不錯的想法

來到蘇寧的首頁https://www.suning.com/,隨便搜個東西,發現換了一個一級域名https://search.suning.com/,看來搜索功能就是在這個域名上實現的了,看看robots爬蟲協議,https://search.suning.com/robots.txt

User-agent: EtaoSpiderDisallow: /User-Agent: *Disallow: /emall/Disallow:/*.doDisallow:/*cityId*Disallow:/*%E4%BB%A3%E5%BC%80%E5%8F%91%E7%A5%A8*/Disallow:/*iy*Disallow:/*sc*

只有不允許爬的這些地址,話說上面那個url編碼%E4%BB%A3%E5%BC%80%E5%8F%91%E7%A5%A8解碼之后是代開發票,哈哈哈,那就別爬這些吧,反正這些都應該被屏蔽了

搜索”網絡設備“進入https://search.suning.com/網絡設備/頁面,可以看到有好多商品,怎么翻都翻不完,能感覺到是動態刷新的,不信咱試試,打開cmd,scrapy shell https://search.suning.com/%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87/這個url編碼就是”網絡設備“加密后的,地址欄可以直接復制,每個商品都是一個li標簽,直接輸出li標簽的個數

>>> len(response.selector.xpath('//ul[@class="general clearfix"]/li').getall()) 30

直接頁面打開f12,用選擇工具找到商品,數出商品的個數可遠遠不止30個,為了排除緩存影響頁面上重新搜索一個“網絡的設備”,別動鼠標滾輪,f12數出來果然是30,看來下拉果然會加載更多商品,在f12里的network選擇XHR,然后頁面往下劃就能看到一個奇怪的東西混入gif大軍

https://search.suning.com/emall/searchV1Product.do?keyword=%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87&ci=157162&pg=01&cp=0&il=0&st=0&iy=0&isDoufu=1&isNoResult=0&n=1&sesab=BCAABBABCAAA&id=IDENTIFYING&cc=351&paging=1&sub=0&jzq=470

經實驗發現paging是切換頁面的關鍵,這個時候是不是就拿去scrapy shell里面嘗試了,但是仔細看看這個路徑/emall/是被robots.txt禁止了的。這里就不建議大家繼續嘗試了,畢竟人家超市都規定寵物不得入內,就沒必要非要帶著小倉鼠進去

如果,我是說如果,還想獲得那些公開數據的話,可以用selenium模擬瀏覽器,或者scrapy里在setting里修改ROBOTSTXT_OBEY = False,線程數量調低,延時調高,一定不要頻繁訪問頁面造成電商服務器損失,畢竟技術本無罪的前提是不打擾人家

所以這個/email/接口的參數就是paging需要遍歷一遍,每個頁面有30個商品信息,然后用xpath選擇器選中每個標簽下的標題,它的內有商品鏈接,訪問鏈接xpath選擇這三個參數返回items就行了

在這里插入圖片描述

由于后臺還在跑上一個爬蟲。。我還是新建一個項目吧,scrapy startproject devices創建項目,cd ./devices/spiders進入爬蟲目錄,scrapy genspider device search.suning.com創建爬蟲

下面直接講解代碼:

setting.py

BOT_NAME = 'devices'SPIDER_MODULES = ['devices.spiders']NEWSPIDER_MODULE = 'devices.spiders'ROBOTSTXT_OBEY = FalseCONCURRENT_REQUESTS = 50DOWNLOAD_DELAY = 1DEFAULT_REQUEST_HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language': 'en','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36','Referer':'https://search.suning.com/',}ITEM_PIPELINES = { 'devices.pipelines.DevicesPipeline': 300,}AUTOTHROTTLE_DEBUG = False# 是否開啟重試RETRY_ENABLED:True# 重試次數RETRY_TIMES:5

setting里都是說過的,之前講的有點亂,建議ctrl+f搜索

items.py

import scrapyclass DevicesItem(scrapy.Item):brand = scrapy.Field()modlenumber = scrapy.Field()producttype = scrapy.Field()

這里在item對象里寫了三個管道里需要用到的變量,品牌,型號,類型,這里的取名最好跟之前cve表名字對應,當然如果自己認識就無所謂了

searchdevice.py

import scrapyfrom devices.items import DevicesItemclass SearchdeviceSpider(scrapy.Spider):name = 'searchdevice'allowed_domains = ['https://search.suning.com/']def start_requests(self):keys = ['手機','電腦','電視','空調','影音','外設','數碼','網絡設備','智能家居',]for key in keys:url = 'https://search.suning.com/emall/searchV1Product.do?keyword={}&ci=157162&pg=01&cp=0&il=0&st=0&iy=0&isDoufu=1&isNoResult=0&n=1&sesab=ACAABBABCCAA&id=IDENTIFYING&cc=351&sub=0&jzq=319&paging='.format(key)# 記錄頁面號yield scrapy.Request(url, dont_filter=True, meta={'i' : 0, 'url' : url})def parse(self, response):# 檢查頁面是否溢出,if len(response.body) 'i' : response.meta['i']+1, 'url' : response.meta['url']}, callback=self.parse)def parse1(self, response):brand = response.selector.xpath('//div[@id="kernelParmeter"]/ul/li/span/a/text()').get()producttype, modlenumber = response.selector.xpath('//div[@id="kernelParmeter"]/ul/li/@title').getall()[1:3]# print('品牌:{},類型:{},型號:{}'.format(brand, producttype, modlenumber))item = DevicesItem()item['brand'], item['producttype'], item['modlenumber'] = brand, producttype, modlenumberreturn item

啟動爬蟲默認讀取start_url列表里或者start_requests()函數里的信息,最開始的url都是從這里生成的,我用搜索關鍵詞替換url,生成可以直接訪問的鏈接,注意我把后面的重要參數&paging=放到了最后,方便下一步的添加頁數,當然經過試驗這樣的url是可以正常訪問的

start_requests()函數調用scrapy.Request(),會默認回調給parse()

parse()內通過html的body長度小于500則該頁面是空的,如果沒超過500就爬取所有商品鏈接通過yield回調給parse1()進行數據的轉存,而yield的特性是在parse1()執行結束之后會返回上一次yield的位置也就是for循環內,知道for循環結束,最后執行一句控制遞歸的yield,他負責給start_requests()生成的url添加頁數并自增

parse1()就是將3個獲取的信息放入item對象里返回給管道存儲

pipelines.py

import pymysqlclass DevicesPipeline:tb = 'device'number = 0def open_spider(self, spider):print("開始爬蟲!")db = spider.settings.get('MYSQL_DB_NAME','cve_db')host = spider.settings.get('MYSQL_HOST','127.0.0.1')port = spider.settings.get('MYSQL_PORT', 3306)user = spider.settings.get('MYSQL_USER','root')passwd = spider.settings.get('MYSQL_PASSWORD','root')self.db_conn =pymysql.connect(host=host, port=port, db=db, user=user, passwd=passwd, charset='utf8')self.db_cur = self.db_conn.cursor()# 三句話為刪表重建,往數據庫補充數據注釋掉self.db_cur.execute("DROP TABLE IF EXISTS %s"%self.tb)sql = """CREATE TABLE IF NOT EXISTS %s (id int PRIMARY KEY AUTO_INCREMENT, brand varchar(56),modlenumber varchar(32),producttype varchar(128)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"""self.db_cur.execute(sql%self.tb)print('建表完成!')def process_item(self, item, spider):if item != None:sql = 'INSERT INTO {}(brand,modlenumber,producttype) VALUES("{}","{}","{}")'.format(self.tb,item['brand'],item['modlenumber'],item['producttype'])print(sql)self.db_cur.execute(sql)self.number += 1if self.number >= 10:self.db_conn.commit()self.number = 0return itemdef close_spider(self, spider):print("結束爬蟲!")self.db_conn.commit()self.db_conn.close()

這個管道就簡單了,啟動爬蟲鏈接數據庫,刪表重建,過程中由爬蟲parse1()傳遞來的信息直接被存入數據庫

不過就在剛剛。。。導致我的ip被網站反爬機制給ban了,于是繼續調低線程,增大延遲,換ip之后繼續,以龜速爬完了1300條,沒聽錯,這站根據我的關鍵詞就只有1300條

在這里插入圖片描述

服了服了,還是換成京東吧,然后使用selenium模擬點擊,

設備信息爬蟲(京東)

試驗之后就感覺selenium不適合弄爬蟲。。。明明就是用做測試自動化的,以前還能做做鼠標驗證碼,現在有機器學習之后誰還用它啊,嗚嗚嗚害得我死機了,根據速度算了下10w條估計得9小時

# -*- coding: utf-8 -*-import scrapyfrom devices_jd.items import DevicesJdItemfrom selenium import webdriverfrom selenium.webdriver.chrome.options import Options# 使用無頭瀏覽器from selenium.webdriver.common.keys import Keysimport timechrome_options = Options()chrome_options.add_argument("--headless")chrome_options.add_argument("--disable-gpu")class DeviceJdSpider(scrapy.Spider):name = 'device_jd'allowed_domains = ['www.jd.com']driver = webdriver.Chrome()url = 'https://search.jd.com/Search?keyword={keyword}&enc=utf-8&wq=%E6%89%8B%E6%9C%BA&pvid=24e8b53d9f164ee092d0bcabf99212d9'keyword = iter(['手機','智能設備','電腦','游戲設備','外設產品','網絡產品','辦公設備','智能家居','生活電器','電視','空調','洗衣機','冰箱','廚衛'])# 實例化一個瀏覽器對象def __init__(self):self.browser = webdriver.Chrome(chrome_options=chrome_options)super().__init__()def start_requests(self):urls = self.url.format(keyword=next(self.keyword))self.driver.get(urls)time.sleep(3)yield scrapy.Request(url=urls, dont_filter=True)def parse(self, response):while True:for _ in range(0,170):self.driver.find_element_by_tag_name('body').send_keys(Keys.ARROW_DOWN) #在這里使用模擬的下方向鍵time.sleep(0.01)time.sleep(0.5)try:producturls=self.driver.find_elements_by_xpath('//div[@class="p-name p-name-type-2"]/a')for producturl in producturls:yield scrapy.Request(url=producturl.get_attribute('href'), callback=self.senditem, dont_filter=True) except Exception as e:print(e)# print(self.driver.find_element_by_class_name('pn-next').get_attribute("title") is "")if self.driver.find_element_by_class_name('pn-next').get_attribute("title") is not "":self.driver.find_element_by_tag_name('body').send_keys(Keys.ARROW_RIGHT)#在這里使用模擬的右方向鍵time.sleep(1)else:breakyield self.start_requests()def senditem(self, response):producttype = response.xpath('//div[@class="item"]/a/text()').extract()[0]brand = response.xpath('//ul[@id="parameter-brand"]/li/a/text()').extract()[0]product = response.xpath('//ul[@class="parameter2 p-parameter-list"]/li/text()').extract()productname = product[0][5:]productid = product[1][5:]item = DevicesJdItem()item['producttype'],item['brand'],item['productname'],item['productid'] = producttype,brand,productname,productid# print('類型:' + producttype)# print('品牌:' + brand)# print('名稱:' + productname)# print('編號:' + productid)try:modlenumber = response.xpath('//dl[@class="clearfix"]/dd/text()').extract()[3]item['modlenumber'] = modlenumber# print('型號:' + modlenumber)except Exception as e:print(e)yield itemdef close(self, spider):self.browser.quit()

大致意思就是根據關鍵詞搜索,這時頁面上有30個商品,然后鍵盤模擬向下按,頁面下劃到底觸發ajax再刷新30個,這時一次性獲取整個頁面的60個商品鏈接送入下一個函數獲取信息存庫,然后模擬鍵盤右鍵翻頁,以此循環

太慢了太慢了,于是重新研究頁面,頁面下滑觸發動態刷新時,f12的network里選擇XHR可以看到有新的請求,頭文件能找到請求的url,類似下面(不影響的參數被我刪了)

https://search.jd.com/Search/s_new.php?keyword=%E6%89%8B%E6%9C%BA%E5%8D%8E%E4%B8%BA&qrst=1&suggest=1.def.0.base&wq=%E6%89%8B%E6%9C%BA%E5%8D%8E%E4%B8%BA&ev=exbrand_%E5%8D%8E%E4%B8%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page=2&s=27&scrolling=y&log_id=1609148178669.7303&tpl=3_M&isList=0

經過不懈努力,我發現了其中奧秘,keyword,wq為搜索內容的url編碼,一個頁面為兩個page,上面是單數下面是偶數,s控制著顯示第*件物品,因為每一整頁會顯示4-6個廣告,我設置page加1則s+28,留出兩個廣告位,即使重復了之后也能在數據庫去重,log_id為當前微秒級的時間戳

代碼不難理解,首先聲明url和搜索關鍵詞keyword,url內預留了參數的位置方便插入,keyword被我改寫成生成器,之后可以使用next()控制循環,(for也可以控制生成器的循環,只是這里不大好用)

class DeviceJdSpider(scrapy.Spider):name = 'device_jd'allowed_domains = ['www.jd.com']url = 'https://search.jd.com/Search/s_new.php?keyword={keyword}&qrst=1&suggest=1.def.0.base&wq={keyword}&ev=exbrand_%E5%8D%8E%E4%B8%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={page}&s={s}&scrolling=y&log_id={time}&tpl=3_M&isList=0'keyword = iter(['手機','智能設備','電腦','游戲設備','外設產品','網絡產品','辦公設備','智能家居','生活電器','電視','空調','洗衣機','冰箱','廚衛'])

然后是開始的url,用time.time()生成時間戳,'%.4f'%(ti*1000)這里乘1000是將秒換成毫秒,與參數s統一格式,回調parse函數并傳入參數keyword,page,s,import time必須放在函數內,,,,不然報錯

def start_requests(self):import timeti = time.time()keyword, page, s, time = next(self.keyword), 1, 1, '%.4f'%(ti*1000)urls = self.url.format(keyword=next(self.keyword), page=page, s=s, time=time)yield scrapy.Request(url=urls, dont_filter=True, meta={'keyword' : keyword, 'page' : 1, 's' : 1})

獲取每個商品的url并遍歷回調給下一個處理函數senditem,然后給page加一并判斷是否超過100頁,如果沒有就遞歸parse函數,如果超過就再次執行start_requests生成新的url,重新執行一輪

def parse(self, response):# 爬取當前頁面每個商品的urlproducturls=response.selector.xpath('//div[@class="p-name p-name-type-2"]/a/@href').getall()for producturl in producturls:producturl = response.urljoin(producturl)yield scrapy.Request(url=producturl, callback=self.senditem, dont_filter=True) import timepage = response.meta['page'] + 1if response.meta['page']
免責聲明:非本網注明原創的信息,皆為程序自動獲取互聯網,目的在于傳遞更多信息,并不代表本網贊同其觀點和對其真實性負責;如此頁面有侵犯到您的權益,請給站長發送郵件,并提供相關證明(版權證明、身份證正反面、侵權鏈接),站長將在收到郵件12小時內刪除。
晨光彩票app-APP全能版下载 <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>