爬蟲的概念
-
模擬瀏覽器發送請求,獲取響應
爬蟲的流程
-
url---》發送請求,獲取響應---》提取數據---》保存
-
發送請求,獲取響應---》提取url(下一頁,詳情頁)重新請求
爬蟲要根據當前url地址對應的響應為準
-
爬蟲只會請求當前這個url,但是不是請求js,
-
瀏覽器拿到的內容,我們在瀏覽器中看到的內容是elements里面的內容
-
elements=url對應的響應+js+css+圖片
requests模塊如何發送請求
-
resposne = requests.get(url)
requests中解決編解碼的方法
-
response.content.decode()
-
response.content.decode("gbk")
-
response.text
判斷請求否是成功
assert response.status_code==200
url編碼
-
https://www.baidu.com/s?wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2
字符串格式化的另一種方式
"x{}xx".format(1)
使用代理ip
-
準備一堆的ip地址,組成ip池,隨機選擇一個ip來時用
-
如何隨機選擇代理ip,讓使用次數較少的ip地址有更大的可能性被用到
-
{"ip":ip,"times":0}
-
[{},{},{},{},{}],對這個ip的列表進行排序,按照使用次數進行排序
-
選擇使用次數較少的10個ip,從中隨機選擇一個
-
-
檢查ip的可用性
-
可以使用requests添加超時參數,判斷ip地址的質量
-
在線代理ip質量檢測的網站
-
攜帶cookie請求
-
攜帶一堆cookie進行請求,把cookie組成cookie池
使用requests提供的session類來請求登陸之后的網站的思路
-
實例化session
-
先使用session發送請求,登錄對網站,把cookie保存在session中
-
再使用session請求登陸之后才能訪問的網站,session能夠自動的攜帶登錄成功時保存在其中的cookie,進行請求
不發送post請求,使用cookie獲取登錄后的頁面
-
cookie過期時間很長的網站
-
在cookie過期之前能夠拿到所有的數據,比較麻煩
-
配合其他程序一起使用,其他程序專門獲取cookie,當前程序專門請求頁面
字典推導式,列表推到是
cookies="anonymid=j3jxk555-nrn0wh; _r01_=1; _ga=GA1.2.1274811859.1497951251; _de=BF09EE3A28DED52E6B65F6A4705D973F1383380866D39FF5; ln_uact=mr_mao_hacker@163.com; depovince=BJ; jebecookies=54f5d0fd-9299-4bb4-801c-eefa4fd3012b|||||; JSESSIONID=abcI6TfWH4N4t_aWJnvdw; ick_login=4be198ce-1f9c-4eab-971d-48abfda70a50; p=0cbee3304bce1ede82a56e901916d0949; first_login_flag=1; ln_hurl=http://hdn.xnimg.cn/photos/hdn421/20171230/1635/main_JQzq_ae7b0000a8791986.jpg; t=79bdd322e760beae79c0b511b8c92a6b9; societyguester=79bdd322e760beae79c0b511b8c92a6b9; id=327550029; xnsid=2ac9a5d8; loginfrom=syshome; ch_id=10016; wp_fold=0"
cookies = {i.split("=")[0]:i.split("=")[1] for i in cookies.split("; ")}
[self.url_temp.format(i * 50) for i in range(1000)]
獲取登錄后的頁面的三種方式
-
實例化session,使用session發送post請求,在使用他獲取登陸后的頁面
-
headers中添加cookie鍵,值為cookie字符串
-
在請求方法中添加cookies參數,接收字典形式的cookie。字典形式的cookie中的鍵是cookie的name對應的值,值是cookie的value對應的值
- response.content.decode("gbk")
- response.text
尋找登錄的post地址
-
在form表單中尋找action對應的url地址
-
post的數據是input標簽中name的值作為鍵,真正的用戶名密碼作為值的字典,post的url地址就是action對應的url地址
-
-
抓包,尋找登錄的url地址
-
勾選perserve log按鈕,防止頁面跳轉找不到url
-
尋找post數據,確定參數
-
參數不會變,直接用,比如密碼不是動態加密的時候
-
參數會變
-
參數在當前的響應中
-
通過js生成
-
-
-
?
定位想要的js
-
選擇會觸發js時間的按鈕,點擊event listener,找到js的位置
-
通過chrome中的search all file來搜索url中關鍵字
-
添加斷點的方式來查看js的操作,通過python來進行同樣的操作
安裝第三方模塊
-
pip install retrying
-
下載源碼解碼,進入解壓后的目錄,
python setup.py install
-
***.whl
安裝方法pip install ***.whl
json使用注意點
-
json中的字符串都是雙引號引起來的
-
如果不是雙引號
-
eval:能實現簡單的字符串和python類型的轉化
-
replace:把單引號替換為雙引號
-
-
-
往一個文件中寫入多個json串,不再是一個json串,不能直接讀取
-
一行寫一個json串,按照行來讀取
-
正則使用的注意點
-
re.findall("a(.*?)b","str")
,能夠返回括號中的內容,括號前后的內容起到定位和過濾的效果 -
原始字符串r,待匹配字符串中有反斜杠的時候,使用r能夠忽視反斜杠帶來的轉義的效果
-
點號默認情況匹配不到
\n
-
\s
能夠匹配空白字符,不僅僅包含空格,還有\t|\r\n
xpath學習重點
-
使用xpath helper或者是chrome中的copy xpath都是從element中提取的數據,但是爬蟲獲取的是url對應的響應,往往和elements不一樣
-
獲取文本
-
a/text()
獲取a下的文本 -
a//text()
獲取a下的所有標簽的文本 -
//a[text()='下一頁']
選擇文本為下一頁三個字的a標簽
-
-
@符號
-
a/@href
-
//ul[@id="detail-list"]
-
-
//
-
在xpath最前面表示從當前html中任意位置開始選擇
-
li//a
表示的是li下任何一個標簽
-
lxml使用注意點
-
lxml能夠修正HTML代碼,但是可能會改錯了
-
使用etree.tostring觀察修改之后的html的樣子,根據修改之后的html字符串寫xpath
-
-
lxml 能夠接受bytes和str的字符串
-
提取頁面數據的思路
-
先分組,渠道一個包含分組標簽的列表
-
遍歷,取其中每一組進行數據的提取,不會造成數據的對應錯亂
-
xpath的包含
-
//div[contains(@class,'i')]
實現爬蟲的套路
-
準備url
-
準備start_url
-
url地址規律不明顯,總數不確定
-
通過代碼提取下一頁的url
-
xpath
-
尋找url地址,部分參數在當前的響應中(比如,當前頁碼數和總的頁碼數在當前的響應中)
-
-
-
準備url_list
-
頁碼總數明確
-
url地址規律明顯
-
-
-
發送請求,獲取響應
-
添加隨機的User-Agent,反反爬蟲
-
添加隨機的代理ip,反反爬蟲
-
在對方判斷出我們是爬蟲之后,應該添加更多的headers字段,包括cookie
-
cookie的處理可以使用session來解決
-
準備一堆能用的cookie,組成cookie池
-
如果不登錄
-
準備剛開始能夠成功請求對方網站的cookie,即接收對方網站設置在response的cookie
-
下一次請求的時候,使用之前的列表中的cookie來請求
-
-
如果登錄
-
準備多個賬號
-
使用程序獲取每個賬號的cookie
-
之后請求登錄之后才能訪問的網站隨機的選擇cookie
-
-
-
-
提取數據
-
確定數據的位置
-
如果數據在當前的url地址中
-
提取的是列表頁的數據
-
直接請求列表頁的url地址,不用進入詳情頁
-
-
提取的是詳情頁的數據
-
-
確定url
-
-
-
發送請求
-
-
-
提取數據
-
-
-
返回
-
-
-
-
如果數據不在當前的url地址中
-
在其他的響應中,尋找數據的位置
-
-
從network中從上往下找
-
-
-
使用chrome中的過濾條件,選擇出了js,css,img之外的按鈕
-
-
-
使用chrome的search all file,搜索數字和英文
-
-
-
-
-
數據的提取
-
xpath,從html中提取整塊的數據,先分組,之后每一組再提取
-
re,提取max_time,price,html中的json字符串
-
json
-
-
?
-
保存
-
保存在本地,text,json,csv
-
保存在數據庫
-
驗證碼的識別
-
url不變,驗證碼不變
-
請求驗證碼的地址,獲得相應,識別
-
-
url不變,驗證碼會變
-
思路:對方服務器返回驗證碼的時候,會和每個用戶的信息和驗證碼進行一個對應,之后,在用戶發送post請求的時候,會對比post請求中法的驗證碼和當前用戶真正的存儲在服務器端的驗證碼是否相同
-
1.實例化session
-
2.使用seesion請求登錄頁面,獲取驗證碼的地址
-
3.使用session請求驗證碼,識別
-
4.使用session發送post請求’
-
-
使用selenium登錄,遇到驗證碼
-
url不變,驗證碼不變,同上
-
url不變,驗證碼會變
-
1.selenium請求登錄頁面,同時拿到驗證碼的地址
-
2.獲取登錄頁面中driver中的cookie,交給requests模塊發送驗證碼的請求,識別
-
3.輸入驗證碼,點擊登錄
-
-
selenium使用的注意點
-
獲取文本和獲取屬性
-
先定位到元素,然后調用
.text
或者get_attribute
方法來去
-
-
selenium獲取的頁面數據是瀏覽器中elements的內容
-
find_element和find_elements的區別
-
find_element返回一個element,如果沒有會報錯
-
find_elements返回一個列表,沒有就是空列表
-
在判斷是否有下一頁的時候,使用find_elements來根據結果的列表長度來判斷
-
-
如果頁面中含有iframe、frame,需要先調用driver.switch_to.frame的方法切換到frame中才能定位元素
-
selenium請求第一頁的時候回等待頁面加載完了之后在獲取數據,但是在點擊翻頁之后,hi直接獲取數據,此時可能會報錯,因為數據還沒有加載出來,需要time.sleep(3)
-
selenium中find_element_by_class_name智能接收一個class對應的一個值,不能傳入多個
?
db.stu.aggregate({$group:{_id:"$name",counter:{$sum:2}}})
?
db.stu.aggregate({$group:{_id:null,counter:{$sum:1}}})
db.stu.aggregate({$group:{_id:"$gender",name:{$push:"$name"}}})
db.stu.aggregate({$group:{_id:"$gender",name:{$push:"$$ROOT"}}})
db.tv3.aggregate({$group:{_id:{"country":"$country",province:"$province",userid:"$userid"}}},{$group:{_id:{country:"$_id.country",province:"$_id.province"},count:{$sum:1}}},{$project:{country:"$_id.country",province:"$_id.province",count:"$count",_id:0}})
db.stu.aggregate(
?{$match:{age:{$gt:20}}},{$group:{_id:"$gender",count:{$sum:1}}})
db.t2.aggregate({$unwind:"$size"})
db.t3.aggregate({$unwind:"$tags"},{$group:{_id:null,count:{$sum:1}}})
db.t3.aggregate({$unwind:{path:"$size",preserveNullAndEmptyArrays:true}}
mongodb插入數據
-
db.collecion.insert({}) 插入數據,
_id
存在就報錯 -
db.collection.save({}) 插入數據,
_id
存在會更新
mongodb的更新操作
-
db.test1000.update({name:"xiaowang"},{name:"xiaozhao"})
-
把name為xiaowang的數據替換為
{name:"xiaozhao"}
-
db.test1000.update({name:"xiaohong"},{$set:{name:"xiaozhang"}})
-
把name為xiaowang的數據name的值更新為xiaozhang
-
db.test1000.update({name:"xiaozhang"},{$set:{name:"xiaohong"}},{multi:true})
-
{multi:true}
達到更新多條的目的
mongodb刪除
-
db.test1000.remove({name:"xiaohong"},{justOne:true})
-
默認情況會刪除所有滿足條件的數據,
{justOne:true}
能達到只刪除一條的效果
mongodb的count方法
-
db.collection.find({條件}).count()
-
db.collection.count({})
mongodb的投影
-
投影:選擇返回結果的字段
-
db.collection.find({條件},{name:1,_id:0})
-
1.
_id
默認會顯示,置為0不顯示 -
2.出了
_id
之外的其他字段,如果不顯示,不寫,不能寫為0
-
$group的注意點
-
$group
對應的字典中有幾個鍵,結果中就有幾個鍵 -
分組依據需要放到
_id
后面 -
取不同的字段的值需要使用$,
$gender
,$age
-
取字典嵌套的字典中的值的時候
$_id.country
-
能夠同時按照多個鍵進行分組
{$group:{_id:{country:"$country",province:"$province"}}}
-
結果是:
{_id:{country:"",province:""}
-
編輯器寫mongodb語句
db.stu.find({$or:[{age:{$gte:20}},{hometown:{$in:["桃花島","華?"]}}]})
?
#按照gender進行分組,獲取不同組數據的個數和平均年齡
db.stu.aggregate({$group:{_id:"$gender",count:{$sum:1},avg_age:{$avg:"$age"}}},{$project:{gender:"$_id",count:1,avg_age:"$avg_age",_id:0}})
# 按照hometown進行分組,獲取不同組的平均年齡
db.stu.aggregate({$group:{_id:"$hometown",mean_age:{$avg:"$age"}}})
#使用$group統計整個文檔
db.stu.aggregate({$group:{_id:null,count:{$sum:1},mean_age:{$avg:"$age"}}})
#選擇年齡大于20的學生,觀察男性和女性有多少人
db.stu.aggregate({$match:{$or:[{age:{$gt:20}},{hometown:{$in:["蒙古","?理"]}}]}},{$group:{_id:"$gender",count:{$sum:1}}},{$project:{_id:0,gender:"$_id",count:1}})
?
mongodb mysql redis的區別和使用場景
-
mysql是關系型數據庫,支持事物
-
mongodb,redis非關系型數據庫,不支持事物
-
mysql,mongodb,redis的使用根據如何方便進行選擇
-
希望速度快的時候,選擇mongodb或者是redis
-
數據量過大的時候,選擇頻繁使用的數據存入redis,其他的存入mongodb
-
mongodb不用提前建表建數據庫,使用方便,字段數量不確定的時候使用mongodb
-
后續需要用到數據之間的關系,此時考慮mysql
-
爬蟲數據去重,實現增量式爬蟲
-
使用數據庫建立關鍵字段(一個或者多個)建立索引進行去重
-
根據url地址進行去重
-
使用場景:
-
url地址對應的數據不會變的情況,url地址能夠唯一判別一個條數據的情況
-
-
思路
-
url存在redis中
-
拿到url地址,判斷url在redis的url的集合中是夠存在
-
存在:說明url已經被請求過,不再請求
-
不存在:url地址沒有被請求過,請求,把該url存入redis的集合中
-
-
布隆過濾器
-
使用多個加密算法加密url地址,得到多個值
-
往對應值的位置把結果設置為1
-
新來一個url地址,一樣通過加密算法生成多個值
-
如果對應位置的值全為1,說明這個url地址已經抓過
-
否則沒有抓過,就把對應位置的值設置為1
-
-
-
根據數據本省進行去重
-
選擇特定的字段,使用加密算法(md5,sha1)講字段進行假面,生成字符串,存入redis的集合中
-
后續新來一條數據,同樣的方法進行加密,如果得到的字符串在redis中存在,說明數據存在,對數據進行更新,否則說明數據不存在,直接插入
-
logging 模塊的使用
-
scrapy
-
settings中設置LOG_LEVEL=“WARNING”
-
settings中設置LOG_FILE="./a.log" #設置日志保存的位置,設置會后終端不會顯示日志內容
-
import logging,實例化logger的方式在任何文件中使用logger輸出內容
-
-
普通項目中
-
import logging
-
logging.basicConfig(...) #設置日志輸出的樣式,格式
-
實例化一個
logger=logging.getLogger(__name__)
-
在任何py文件中調用logger即可
-
crawlspider的使用
-
常見爬蟲 scrapy genspider -t crawl 爬蟲名 allow_domain
-
指定start_url,對應的響應會進過rules提取url地址
-
完善rules,添加Rule
Rule(LinkExtractor(allow=r'/web/site0/tab5240/info\d+\.htm'), callback='parse_item'),
-
注意點:
-
url地址不完整,crawlspider會自動補充完整之后在請求
-
parse函數不能定義,他有特殊的功能需要實現
-
callback:連接提取器提取出來的url地址對應的響應交給他處理
-
follow:連接提取器提取出來的url地址對應的響應是否繼續被rules來過濾
-
request對象什么時候入隊
-
dont_filter = True ,構造請求的時候,把dont_filter置為True,該url會被反復抓取(url地址對應的內容會更新的情況)
-
一個全新的url地址被抓到的時候,構造request請求
-
url地址在start_urls中的時候,會入隊,不管之前是否請求過
-
構造start_url地址的請求時候,dont_filter = True
-
def enqueue_request(self, request):if not request.dont_filter and self.df.request_seen(request):# dont_filter=False Ture True request指紋已經存在 #不會入隊# dont_filter=False Ture False request指紋已經存在 全新的url #會入隊# dont_filter=Ture False #會入隊self.df.log(request, self.spider)return Falseself.queue.push(request) #入隊return True
scrapy_redis去重方法
-
使用sha1加密request得到指紋
-
把指紋存在redis的集合中
-
下一次新來一個request,同樣的方式生成指紋,判斷指紋是否存在reids的集合中
生成指紋
fp = hashlib.sha1()
fp.update(to_bytes(request.method)) ?#請求方法
fp.update(to_bytes(canonicalize_url(request.url))) #url
fp.update(request.body or b'') ?#請求體
return fp.hexdigest()
判斷數據是否存在redis的集合中,不存在插入
added = self.server.sadd(self.key, fp)
return added != 0
爬蟲項目
-
項目名字
-
request+selenium爬蟲
-
-
項目周期
-
項目介紹
-
爬了XXXXX,XXX,XXX,等網站,獲取網站上的XXX,XXX,XXX,數據,每個月定時抓取XXX數據,使用該數據實現了XXX,XXX,XX,
-
-
開發環境
-
linux+pycharm+requests+mongodb+redis+crontab+scrapy_redis+ scarpy + mysql+gevent+celery+threading
-
-
使用技術
-
使用requests...把數據存儲在mongodb中
-
使用crontab實現程序的定時啟動抓取
-
url地址的去重
-
使用redis的集合,把request對象的XXX字段通過sha1生成指紋,放入redis的集合中進行去重,實現基于url地址的增量式爬蟲
-
布隆過濾
-
-
對數據的去重
-
把數據的XXX字段通過sha1生成指紋,放入redis的集合中進行去重,實現增量式爬蟲
-
-
反扒
-
代理ip
-
購買了第三的代理ip,組成代理ip池,其中的ip沒兩天更新一次,同時使用單獨的程序來檢查代理ip的可用
-
-
cookie
-
準備了XX個賬號,使用requests獲取賬號的對應的cookie,存儲在redis中,后續發送請求的時候隨機選擇cookie
-
使用selenium來進行模擬登陸,獲取cookie,保存在Redis中
-
-
數據通過js生成
-
分析js,通過chrome瀏覽器定位js的位置,尋找js生成數據的方式
-
通過selenium來模擬頁面的加載內容,獲取頁面動態加載后的數據
-
-
-
提高爬蟲效率
-
使用多線,線程池,協程,celery來完成爬蟲
-
使用scrapy框架來實現爬蟲,
-
不能斷點續爬,請求過的url地址不能持久化
-
使用scrapy_redis
-
-
不能對數據進行去重
-
把數據的XXX字段通過sha1生成指紋,放入redis的集合中進行去重,實現增量式爬蟲
-
-
-
scrapy_redis
-
domz實現增量式,持久化的爬蟲
-
實現分布式爬蟲
-
-
-
-
項目名字
-
scarpy爬蟲
-
-
項目周期
-
項目介紹
-
爬了XXXXX,XXX,XXX,等網站,獲取網站上的XXX,XXX,XXX,數據,每個月定時抓取XXX數據,使用該數據實現了XXX,XXX,XX,
-
-
開發環境
-
linux+pycharm+requests+mongodb+redis+crontab+scrapy_redis+ scarpy + mysql+gevent+celery+threading
-
-
使用技術
-
使用requests...把數據存儲在mongodb中
-
使用crontab實現程序的定時啟動抓取
-
url地址的去重
-
使用redis的集合,把request對象的XXX字段通過sha1生成指紋,放入redis的集合中進行去重,實現基于url地址的增量式爬蟲
-
布隆過濾
-
-
對數據的去重
-
把數據的XXX字段通過sha1生成指紋,放入redis的集合中進行去重,實現增量式爬蟲
-
-
反扒
-
代理ip
-
購買了第三的代理ip,組成代理ip池,其中的ip沒兩天更新一次,同時使用單獨的程序來檢查代理ip的可用
-
-
cookie
-
準備了XX個賬號,使用requests獲取賬號的對應的cookie,存儲在redis中,后續發送請求的時候隨機選擇cookie
-
使用selenium來進行模擬登陸,獲取cookie,保存在Redis中
-
-
數據通過js生成
-
分析js,通過chrome瀏覽器定位js的位置,尋找js生成數據的方式
-
通過selenium來模擬頁面的加載內容,獲取頁面動態加載后的數據
-
-
-
提高爬蟲效率
-
使用多線,線程池,協程,celery來完成爬蟲
-
使用scrapy框架來實現爬蟲,
-
不能斷點續爬,請求過的url地址不能持久化
-
使用scrapy_redis
-
-
不能對數據進行去重
-
把數據的XXX字段通過sha1生成指紋,放入redis的集合中進行去重,實現增量式爬蟲
-
-
-
scrapy_redis
-
domz實現增量式,持久化的爬蟲
-
實現分布式爬蟲
-
-
-