python 爬蟲二

內容回顧

- 模擬登錄:
- sometimes咱們須要爬取基於當前用戶的用戶信息(須要登陸後纔可查看)
- 實現流程:
- 藉助於抓包工具,抓取點擊登陸按鈕發起的post請求(url,參數(動態參數))
- 攜帶cookie對其餘子頁面進行請求發送
- 反爬機制:
- robots
- UA檢測
- 驗證碼
- cookie
- 禁ip
- 動態請求參數

在程序中 是否能夠一味的使用多線程,多進程?php

(推薦)使用單線程+多任務異步協程

event_loop:事件循環,至關於一個無限循環,咱們能夠把一些特殊函數註冊(放置)到這個事件循環上,當知足某些條件的時候,函數就會被循環執行。
程序是按照設定的順序從頭執行到尾,運行的次數也是徹底按照設定。當在編寫異步程序時,必然其中有部分程序的運行耗時是比較久的,須要先讓出當前程序的控制權,讓其在背後運行,讓另外一部分的程序先運行起來。
當背後運行的程序完成後,也須要及時通知主程序已經完成任務能夠進行下一步操做,但這個過程所需的時間是不肯定的,須要主程序不斷的監聽狀態,一旦收到了任務完成的消息,就開始進行下一步。loop就是這個持續不斷的監視器。
coroutine:中文翻譯叫協程,在 Python 中常指代爲協程對象類型,咱們能夠將協程對象註冊到事件循環中,
它會被事件循環調用。咱們可使用 async 關鍵字來定義一個方法,這個方法在調用時不會當即被執行,
而是返回一個協程對象。
task:任務,它是對協程對象的進一步封裝,包含了任務的各個狀態。
future:表明未來執行或尚未執行的任務,實際上和 task 沒有本質區別。
另外咱們還須要瞭解 async/await 關鍵字,它是從 Python 3.6 纔出現的,專門用於定義協程。其中,async 定義一個協程,await 用來掛起阻塞方法的執行。
瞭解攜程概念基礎後,咱們來看看一個協程,具體須要什麼?

事件循環-就是一個無限的循環
協程:特殊的函數 async修飾以後 這個函數調用不會馬上執行 會封轉到協程對象,只有協程註冊到任務對象中,而且任務對象註冊事件循環中 纔會執行
await:只要有阻塞操做 必定要在前面加await

協程基礎

import asyncio
async def request(url):
    print('正在請求',url)
    print('下載成功',url)
c=request('www.baidu.com')

#實例化一個事件對象
loop=asyncio.get_event_loop()
#建立一個任務對象 將協程封裝到該對象中 #task=loop.create_task(c) #第二種任務對象 task=asyncio.ensure_future(c) #將協程對象註冊到事件循環對象中,並啓動事件循環對象 loop=run_until_complete(task)

多任務異步協程

from time import sleep
import asyncio
import time
urls = ['www.baidu.com','www.sogou.com','www.goubanjia.com']
start = time.time()
async def request(url):
    print('正在請求:',url)
    #在多任務異步協程實現中,不能夠出現不支持異步的相關代碼。
    # sleep(2)
    await asyncio.sleep(2)
    print('下載成功:',url)

#實例化一個事件循環對象
loop = asyncio.get_event_loop()

#任務列表:放置多個任務對象
tasks = []
for url in urls:
    c = request(url)
    #實例化任務對象的方法
    task = asyncio.ensure_future(c)
    tasks.append(task)

#將協程對象註冊到事件循環中,而且咱們須要啓動事件循環
loop.run_until_complete(asyncio.wait(tasks))

print(time.time()-start)

多任務異步協程應用到爬蟲中

能夠看到 每次訪問 都須要2秒 一種是6秒html

from flask import Flask
import time

app = Flask(__name__)


@app.route('/bobo')
def index_bobo():
    time.sleep(2)
    return 'Hello bobo'

@app.route('/jay')
def index_jay():
    time.sleep(2)
    return 'Hello jay'

@app.route('/tom')
def index_tom():
    time.sleep(2)
    return 'Hello tom'

if __name__ == '__main__':
    app.run(threaded=True)
#aiohttp:支持異步的一個基於網絡請求的模塊
# pip install aiohttp
import asyncio
import aiohttp
import time
#單線程+多任務異步協程
urls = [
    'http://127.0.0.1:5000/jay',
    'http://127.0.0.1:5000/bobo',
    'http://127.0.0.1:5000/tom'
]
#代理操做有變化:
#async with await s.get(url,proxy="http://ip:port") as response:
async def get_pageText(url):
    async with aiohttp.ClientSession() as s:#實例化一個請求對象
        async  with await s.get(url) as response:#get方法
            page_text=await response.text()#只要有阻塞操做就使用await 獲取返回值
            #藉助回調函數進行響應數據的解析操做
            return page_text
#封裝回調函數用於數據解析
def parse(task):
    #1.獲取響應數據
    page_text = task.result()
    print(page_text+',即將進行數據解析!!!')
    #解析操做寫在該位置

start=time.time()
tasks=[]
for url in urls:
    c=get_pageText(url)#使用這個函數
    task=asyncio.ensure_future(c)#對協程的一種封裝
    task.add_done_callback(parse) # 給任務對象綁定回調函數用於數據解析
    tasks.append(task)

loop=asyncio.get_event_loop()#建立一個事件對象
loop.run_until_complete(asyncio.wait(tasks))#將協程註冊到循環中,並啓用這個循環
print(time.time()-start)

 selenium

概念:是一個基於瀏覽器自動話的模塊.python

和爬蟲之間的關聯?mysql

  幫咱們便捷的爬取到頁面中動態加載出來的數據jquery

  實現模擬登錄linux

使用流程:web

pip install selenium

下載對應的驅動程序:http://chromedriver.storage.googleapis.com/index.htmlredis

查看對應的版本:https://blog.csdn.net/huilan_same/article/details/51896672sql

selenium簡單應用

from  selenium import webdriver
from lxml import etree
import  time
bro=webdriver.Chrome(executable_path='./chromedriver.exe')#導入驅動
#讓瀏覽器對指定url發起訪問
bro.get('http://125.35.6.84:81/xk/')
#獲取瀏覽器當前打開頁面源碼(可見既可得)
page_text=bro.page_source
time.sleep(2)
tree=etree.HTML(page_text)
name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0]
print(name)
time.sleep(2)
bro.quit()
####################打開淘寶輸入想搜索的值###########################
from selenium import webdriver
import time
bro=webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://www.taobao.com')
#節點定位find系列的方法
input_text=bro.find_element_by_id('q')#找到id爲q的
#節點交互
input_text.send_keys('蘋果')#在text中輸入蘋果
time.sleep(2)

動做鏈

from selenium import webdriver
#導入動做鏈對應的模塊
from  selenium.webdriver import ActionChains
import time
bro=webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
#若是定位的節點是被包含在iframes節點之中的,則必須使用switch_to進行frame的切換
bro.switch_to.frame('iframeResult') #iframe

div_tag=bro.find_element_by_id('draggable')#獲取id
#實例化一個動做鏈對象(須要將瀏覽器對象做爲參數傳遞給該對象的構造方法)
action=ActionChains(bro)
#單擊且長按
action.click_and_hold(div_tag)

for i in range(5):
    #讓div向右移動
    action.move_by_offset(17,0).perform()#移動17*5 perform馬上執行動做鏈
    time.sleep(0.5)
time.sleep(2)
bro.quit()#退出

無頭瀏覽器

from selenium import webdriver
from lxml import etree
import time

from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options)
#讓瀏覽器對指定url發起訪問
bro.get('http://125.35.6.84:81/xk/')

#獲取瀏覽器當前打開頁面的頁面源碼數據(可見便可得)
page_text = bro.page_source
time.sleep(2)
tree = etree.HTML(page_text)
name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0]
print(name)
time.sleep(3)
bro.quit()

selenium規避被檢測

from selenium import webdriver
from lxml import etree
import time
#規避檢查
from selenium.webdriver import ChromeOptions
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])

bro = webdriver.Chrome(executable_path='./chromedriver.exe',options=option)
#讓瀏覽器對指定url發起訪問
bro.get('http://125.35.6.84:81/xk/')

#獲取瀏覽器當前打開頁面的頁面源碼數據(可見便可得)
page_text =bro.page_source
time.sleep(2)
tree=etree.HTML(page_text)
name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0]
print(name)
time.sleep(5)
bro.quit()

模擬QQ空間

import request
from selenium import webdriver
import time

driver=webdriver.Chrome(executable_path='./chromedriver.exe')
driver.get('https://qzone.qq.com/')
# 在web 應用中常常會遇到frame 嵌套頁面的應用,使用WebDriver 每次只能在一個頁面上識別元素,對於frame 嵌套內的頁面上的元素,直接定位是定位是定位不到的。這個時候就須要經過switch_to_frame()方法將當前定位的主體切換了frame 裏。
driver.switch_to.frame('login_frame')
driver.find_element_by_id('switcher_plogin').click()
driver.find_element_by_id('u').send_keys('1820405927')  # 這裏填寫你的QQ號
driver.find_element_by_id('p').send_keys('xxxxxx')  # 這裏填寫你的QQ密碼
driver.find_element_by_id('login_button').click()
time.sleep(2)
driver.close()

pyppeteer 

 類是於一個centos的selenium 使用谷歌測試版的瀏覽器

 

超級鷹自動登錄12306

pip install Pillowchrome

chaojiying_api.py

import requests
from hashlib import md5


class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
        self.username = username
        password = password.encode('utf8')
        self.password = md5(password).hexdigest()
        self.soft_id = soft_id  # 898175
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,  # 898175
        }
        self.headers = {
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }

    def PostPic(self, im, codetype):
        """
        im: 圖片字節
        codetype: 題目類型 參考 http://www.chaojiying.com/price.html
        """
        params = {
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
                          headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:報錯題目的圖片ID
        """
        params = {
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
        return r.json()

12306程序

from selenium import webdriver
import time
from selenium.webdriver import ActionChains
from PIL import Image#切圖
from chaojiying_api import Chaojiying_Client
bro=webdriver.Chrome(executable_path=r'./chromedriver.exe')
bro.get('https://kyfw.12306.cn/otn/login/init')

time.sleep(2)
code_img_ele=bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
time.sleep(2)
#驗證碼圖片的左上角的座標
location=code_img_ele.location#x,y
print('驗證碼圖片左上角的座標',location)
size=code_img_ele.size#驗證碼圖片的長和寬
print('驗證碼圖片的長和寬size',size)
#驗證碼左上角和右下角這2個點的座標
rangle=(
    int(location['x']),
    int(location['y']),
    int(location['x'] + size['width']),
    int(location['y'] + size['height'])

)
#截取當前瀏覽器打開的這張頁面的圖像
bro.save_screenshot('aa.png')

i=Image.open('./aa.png')#讀取這個圖片
#截取下來驗證碼圖片名稱
code_img_name='./code.png'
#crop就能夠根據左上角 和右下角的座標進行指定區域的截取
frame=i.crop(rangle)
frame.save(code_img_name)

import requests
chaojiying = Chaojiying_Client('xuebaohua', '6xbh', '898175')  # 用戶中心>>軟件ID 生成一個替換 96001
im=open('./code.png','rb').read()
result=chaojiying.PostPic(im,9004)#9004驗證類型
print("***************", result)
result=result['pic_str']#取得座標
all_list = [] #[[x1,y1],[x2,y2],[x3,y3]]
if '|' in result:  # 117,75|188,146|117,75
    list_1 = result.split('|')
    count_1 = len(list_1)
    for i in range(count_1):
        xy_list = []
        x = int(list_1[i].split(',')[0])   #[[x,y],[]]
        y = int(list_1[i].split(',')[1])
        xy_list.append(x)
        xy_list.append(y)
        all_list.append(xy_list)
    print(all_list,'11111')#存儲的形式 [[260, 62], [46, 139]] 11111
else:
    x = int(result.split(',')[0])   #[[x,y]]
    y = int(result.split(',')[1])
    xy_list = []
    xy_list.append(x)
    xy_list.append(y)
    all_list.append(xy_list)
print(all_list)#[[251, 76]]
code_img = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')

action = ActionChains(bro)#進行動做鏈
for l in all_list:
    x=l[0]
    y=l[1]                                       #拿到截圖圖片進行過便宜
    ActionChains(bro).move_to_element_with_offset(code_img, x, y).click().perform()

bro.find_element_by_id('username').send_keys('135022')
time.sleep(2)
bro.find_element_by_id('password').send_keys('166')
time.sleep(2)
bro.find_element_by_id('loginSub').click()


'''
驗證碼圖片左上角的座標 {'x': 278, 'y': 274}
驗證碼圖片的長和寬size {'height': 190, 'width': 293}
*************** {'err_no': 0, 'err_str': 'OK', 'pic_id': '3069614092051000215', 'pic_str': '251,76', 'md5': '93d0b89555d109be569a44c5a3b997fe'}
[[251, 76]]
'''
'''
#2個12306驗證碼的數據
驗證碼圖片左上角的座標 {'x': 278, 'y': 274}
驗證碼圖片的長和寬size {'height': 190, 'width': 293}
*************** {'err_no': 0, 'err_str': 'OK', 'pic_id': '9069614192051000217', 'pic_str': '260,62|46,139', 'md5': '818ec8e895f95dff73a86583890038b0'}
[[260, 62], [46, 139]] 11111
[[260, 62], [46, 139]]
'''

scrapy:爬蟲框架 異步爬取,高性能的數據解析+持久化存儲操做

框架:集成了不少功能且具備很強通用性的一個項目模版

如何學習框架:

  學習框架的功能模塊的具體使用

環境的安裝:

linux:

  pip install scrapy

windows:

      a. pip3 install wheel

      b. 下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

      c. 進入下載目錄,執行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl#藉助實現異步

      d. pip3 install pywin32

      e. pip3 install scrapy

scrapy:爬取糗事百科文字

使用流程

  建立一個工程:scrapy startproject firstBlood

  cd firstBlood

  建立爬蟲文件: scrapy genspider first www.baiud.com

  執行工程:scrapy crawl first --nolog(可選)

命令行的:scrapy crawl qiutu -o qiutu.csv  執行保存(侷限性比較強,只能是.csv)

基於管道:

命令行代碼

# -*- coding: utf-8 -*-
import scrapy

#爬蟲類
class FirstSpider(scrapy.Spider):
    #爬蟲文件的名稱
    name = 'qiutu'
    #容許的域名
    # allowed_domains = ['www.baiud.com']
    #起始的url列表 被進行自動的請求發送
    start_urls = ['https://www.qiushibaike.com/text/']

    #用來解析數據
    #持久化存儲,只能夠將parse方法的返回值存儲到磁盤文件
    def parse(self, response):
        div_list=response.xpath('//*[@id="content-left"]/div')
        all_data=[]
        for div in div_list:
            # author=div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content=div.xpath('.//div[@class="content"]/span//text()').extract()
            content=''.join(content)#將列表轉成字符串
            dic={
                'author':author,
                'content':content
            }
            all_data.append(dic)
        return  all_data

持久化存儲代碼

items.py

import scrapy


class FirstbloodItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()#萬能的數據類型
    author = scrapy.Field()
    content = scrapy.Field()

first.py

from ..items import FirstbloodItem
#爬蟲類
class FirstSpider(scrapy.Spider):
    #爬蟲文件的名稱
    name = 'qiutu'
    #容許的域名
    # allowed_domains = ['www.baiud.com']
    #起始的url列表 被進行自動的請求發送
    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:
            # author=div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            # print(author,'11111')
            content=div.xpath('.//div[@class="content"]/span//text()').extract()
            content=''.join(content)#將列表轉成字符串
            #實例化一個item類型的對象
            item=FirstbloodItem()
            #使用中括號的形式訪問item對象中的屬性
            item['author'] = author
            item['content'] = content
            #將item內容提交到管道
            yield item

pipelines.py

import pymysql
from redis import Redis
class FirstbloodPipeline(object):
    fp = None
    def open_spider(self,spider):#修改父類方法
        print('開始爬蟲.......')
        self.fb=open('./qiutu.txt','w',encoding='utf-8')
    def process_item(self, item, spider):
        author=item['author']#取屬性賦值
        content = item['content']
        self.fb.write(author+':'+content+'\n')
        return item #返回給了下一個即將被執行的管道類
    def close_spider(self,spider):
        print('結束爬蟲!!!')
        self.fb.close()

class FirstbloodPipeline(object):
    conn=None
    cursor=None
    def open_spider(self,spider):
        self.conn=pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456.com',db='qiutu')
        print(self.conn)
    def process_item(self,item,spider):
        self.cursor=self.conn.cursor()
        try:
            self.cursor.execute('insert into qiutu values("%s","%s")'%(item['author'],item['content']))
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback()#回滾
        return item
    def close_spider(self,seider):
        self.cursor.close()
        self.conn.close()

scrapy:使用總結

建立工程 scrapy startproject proname
建立爬蟲文件
    cd proname
    scrapy genspider spidername www.baidu.com
爬蟲相關屬性方法
    爬蟲文件的名稱:name
    起始的url列表:start_urls 存儲的url會被scrapy進行自動的請求發送
    parse(reponse):用來解析start_urls列表中url對應的響應數據
    response.xpath() extrct()
數據持久化存儲
-基於終端指定
  只能夠將parse方法的返回值進行持久化存儲
  scrapy crawl spidername -o ./file
-基礎管道持久化存儲的編碼流程
  數據解析
  -在item類中聲明相關的屬性用於存儲解析到數據
  -將解析到的數據存儲封裝到item類型對象中
  -將item對象提交給管道類
  -item會被管道類中的process_item方法的item參數進行接收
  -process_item方法中編寫基於item持久化存儲的操做
  -在配置文件中開啓管道
管道細節處理:
  -管道文件中一個類對應的是什麼?
    一個類表達式將解析到數據存儲到某一個具體的平臺中
  -process_item方法中的返回值表示什麼含義
    return item就是說將item傳遞給下一個即將被執行的管道類
  -open_spider#開始 close_spider#結束


手動請求發送

yield scrapy.Request(url,callback):callback回調一個函數用於數據解析

爬取陽光網前5頁數據

import scrapy
from sunLinePro.items import SunlineproItem

class SunSpider(scrapy.Spider):
    name = 'sun'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']

    #通用的url模板(不能夠修改)
    url = 'http://wz.sun0769.com/index.php/question/questionType?type=4&page=%d'
    page = 1

    def parse(self, response):
        print('--------------------------page=',self.page)
        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]/text()').extract_first()
            status = tr.xpath('./td[3]/span/text()').extract_first()

            item = SunlineproItem()
            item['title'] = title
            item['status'] = status

            yield item
        if self.page < 5:
            #手動對指定的url進行請求發送
            count = self.page * 30
            new_url = format(self.url%count)
            self.page += 1
            # 手動對指定的url進行請求發送
            yield scrapy.Request(url=new_url,callback=self.parse)

post請求發送和cookie的處理

- post請求的發送:(麻煩少用,登錄用requests)
- 重寫父類的start_requests(self)方法
- 在該方法內部只須要調用yield scrapy.FormRequest(url,callback,formdata)
- cookie處理:scrapy默認狀況下會自動進行cookie處理 

#COOKIES_ENABLED = False 若是把註釋取消就不會存儲
import scrapy


class PostdemoSpider(scrapy.Spider):
    name = 'postDemo'
    # allowed_domains = ['www.xxx.com']
    #https://fanyi.baidu.com/sug
    start_urls = ['https://fanyi.baidu.com/sug']
    #父類方法,就是將start_urls中的列表元素進行get請求的發送
    # def start_requests(self):
    #     for url in self.start_urls:
    #         yield scrapy.Request(url=url,callback=self.parse)

    def start_requests(self):
        for url in self.start_urls:
            data = {
                'kw':'cat'
            }
            #post請求的手動發送使用的是FormRequest
            yield scrapy.FormRequest(url=url,callback=self.parse,formdata=data)

    def parse(self, response):
        print(response.text)

請求傳參:

- 使用場景:若是使用scrapy爬取的數據沒有在同一張頁面中,則必須使用請求傳參

        - 基於起始url進行數據解析(parse)
            - 解析數據
                - 電影的名稱
                - 詳情頁的url
                - 對詳情頁的url發起手動請求(指定的回調函數parse_detail),進行請求傳參(meta)
                    meta傳遞給parse_detail這個回調函數
                - 封裝一個其餘頁碼對應url的一個通用的URL模板
                - 在for循環外部,手動對其餘頁的url進行手動請求發送(須要指定回調函數==》parse)
            - 定義parse_detail回調方法,在其內部對電影的簡介進行解析。解析完畢後,須要將解析到的電影名稱
                和電影的簡介封裝到同一個item中。
                - 接收傳遞過來的item,而且將解析到的數據存儲到item中,將item提交給管道

 

5大核心組鍵

spider對url進行請求對象封裝 傳給引擎,轉發給調度器

調度器有 隊列,過濾器2個部分,將過濾請求加入到隊列中

調度器在把返回的請求經過引擎 給下載器

下載器在上互聯網上進行下載,互聯網response給下載器

下載器給引擎,引擎給splder,產生item再給引擎 再給管道

引擎做用:解析全部數據,事物觸發

 =====請求傳參抓前5頁內容

# -*- coding: utf-8 -*-
import scrapy
from ..items import MovieproItem

class MovieSpider(scrapy.Spider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567tv.tv/frim/index1.html']

    #通用的url模板只適用於非第一頁
    url = 'https://www.4567tv.tv/frim/index1-%d.html'
    page = 2

    #電影名稱(首頁),簡介(詳情頁)
    def parse(self, response):
        li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
        for li in li_list:
            name = li.xpath('./div/a/@title').extract_first()#電影名稱
            detail_url = 'https://www.4567tv.tv'+li.xpath('./div/a/@href').extract_first()#電影詳情url
            item = MovieproItem()
            item['name'] = name

            #對詳情頁的url發起get請求
            #請求傳參:meta參數對應的字典就能夠傳遞給請求對象中指定好的回調函數
            yield scrapy.Request(url=detail_url,callback=self.detail_parse,meta={'item':item})
        if self.page <= 5:
            new_url = format(self.url%self.page)#第二頁
            self.page += 1
            yield scrapy.Request(url=new_url,callback=self.parse)#再次調page
    #解析詳情頁的頁面數據
    def detail_parse(self,response):
        #回調函數內部經過response.meta就能夠接收到請求傳參傳遞過來的字典
        item = response.meta['item']
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['desc'] = desc
        yield item

ua假裝 代理ip

下載中間件:批量攔截全部的請求和相應啊

攔截請求: UA假裝 代理ip

 

from scrapy import signals
import random

#批量攔截全部的請求和響應
class MiddlewearproDownloaderMiddleware(object):
    #UA池
    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",
    ]
    #代理池
    PROXY_http = [
        '153.180.102.104:80',
        '195.208.131.189:56055',
    ]
    PROXY_https = [
        '120.83.49.90:9000',
        '95.189.112.214:35508',
    ]

    #攔截正常請求:request就是該方法攔截到的請求,spider就是爬蟲類實例化的一個對象
    def process_request(self, request, spider):
        print('this is process_request!!!')
        #UA假裝
        request.headers['User-Agent'] = random.choice(self.user_agent_list)
        return None

    #攔截全部的響應
    def process_response(self, request, response, spider):

        return response

    #攔截髮生異常的請求對象
    def process_exception(self, request, exception, spider):
        print('this is process_exception!!!!')
        #代理ip的設定
        if request.url.split(':')[0] == 'http':
            request.meta['proxy'] = random.choice(self.PROXY_http)
        else:
            request.meta['proxy'] = random.choice(self.PROXY_https)

        #將修正後的請求對象從新進行請求發送
        return request

scrapy+selenium 進行相應篡改

(慢死 有時候還不對)我的評價

scrapy中應用selenium的編碼流程:
    - 爬蟲類中定義一個屬性bro
    - 爬蟲類中重寫父類的一個方法closed,在該方法中關閉bro
    - 在中間件類的process_response中編寫selenium自動化的相關操做

wangyi163.py

import scrapy

from ..items import WangyiItem
from selenium import webdriver

class Wangye163Spider(scrapy.Spider):
    name = 'wangye163'
    # allowed_domains = ['www.163.com']
    start_urls = ['https://news.163.com/']
    # 瀏覽器實例化的操做只會被執行一次
    bro = webdriver.Chrome(executable_path='D:\老男孩老師筆記\第六階段爬蟲\pachong練習\day06\wangyi\wangyi\spiders\chromedriver.exe')

    urls = []  # 5大板塊對應的url 發送給中間件

    def parse(self, response):
        li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
        for index in [3, 4, 6, 7, 8]:
            li = li_list[index]  # 查找第幾個位子
            new_url = li.xpath('./a/@href').extract_first()
            print(new_url,1111)
            self.urls.append(new_url)
            # 五大板塊對應的url進行請求發送
            yield scrapy.Request(url=new_url, callback=self.parse_news)

    # 用來解析每個板塊對應的新聞數據(新聞標題)
    def parse_news(self, response):
        div_list = response.xpath('//div[@class="ndi_main"]/div')
        for div in div_list:
            try:
                title = div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first()
            except:
                continue
            new_detail_url = div.xpath('.//div[@class="news_title"]/h3/a/@href').extract_first()
            # 實例化item對象將解析內容存儲到item對象中
            item = WangyiItem()
            item['title'] = title
            # 對詳情頁的url手動請求發送以便回去新聞內容   content在另外一個解析對象中,因此要進行傳參
            yield scrapy.Request(url=new_detail_url, callback=self.parse_detail, meta={'item': item})

    def parse_detail(self, response):  # 解析方法
        item = response.meta['item']  # 這時候就能夠拿到item對象了

        # 經過response解析出新聞內容
        content = response.xpath('//div[@id="endText"]/p//text()').extract()
        if not content:
            content = response.xpath('//div[@class="viewport"]/div[@class="overview"]/p/text()').extract_first()
        # else:
        #     content = response.xpath('//div[@id="endText"]//text()').extract()
        content = ''.join(content).strip()
        item['content'] = content
        yield item

    def closed(self, spider):  # 重寫父類的關閉方案法
        print('爬蟲系統結束')
        self.bro.quit()

中間件

from scrapy import signals
from scrapy.http import HtmlResponse
from time import sleep

class WangyiDownloaderMiddleware(object):
    def process_request(self, request, spider):
        return None
        #在5五大板塊中的url 若是是攔擊response
    def process_response(self, request, response, spider):
        if request.url in spider.urls:  # 爬蟲類中spider對贏得urls屬性
            # 就要對其對應的相應對象進行處理

            # 獲取了在爬中類中定義好的瀏覽器對象
            bro = spider.bro
            bro.get(request.url)  # 獲取攜帶了新聞數據頁面源碼數據
            '''
            bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            '''

            page_text = bro.page_source  # 實例化一個新的響應對象 age_text是符合要求的裏面包含了動態數據
            new_response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
            #知足條件的response
            return new_response
        else:
            return response

    def process_exception(self, request, exception, spider):
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

 CrawlSpider

做用:就是用於進行全站數據的爬取
- CrawlSpider就是Spider的一個子類

- 如何新建一個基於CrawlSpider的爬蟲文件
- scrapy genspider -t crawl xxx www.xxx.com

- LinkExtractor鏈接提取器:根據指定規則(正則)進行鏈接的提取

- Rule規則解析器:將連接提取器提取到的連接進行請求發送,而後對獲取的頁面數據進行指定規則(callback)的解析

- 一個連接提取器對應惟一一個規則解析器

dianyin

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import SunlineproItem,ContentItem

class DianyiSpider(CrawlSpider):
    name = 'dianyi'
    # 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+')#提取頁碼鏈接

    link1 = LinkExtractor(allow=r'question/2019\d+/\d+\.shtml')  # 提取詳情頁的連接

    rules = (
        Rule(link, callback='parse_item', follow=False),#等於true就會自動爬取全部頁
        Rule(link1, callback='parse_detail'),
    )
    #解析出標題和網友名稱
    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]/text()').extract_first()
            netFriend = tr.xpath('./td[4]/text()').extract_first()
            item = SunlineproItem()#沒有辦法進行傳參請求 因此弄了2個item
            item['title'] = title
            item['netFriend'] = netFriend

            yield item
    #解析出新聞的內容
    def parse_detail(self,response):
        content = response.xpath('/html/body/div[9]/table[2]//tr[1]/td/text()').extract()
        content = ''.join(content)
        print(content,3333333333)
        item = ContentItem()
        item['content'] = content

        yield item

items

import scrapy
class SunlineproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title=scrapy.Field()
    netFriend = scrapy.Field()

class ContentItem(scrapy.Item):
    # define the fields for your item here like:
    content = scrapy.Field()

pipelines

class SunlineproPipeline(object):
    def process_item(self, item, spider):
        #接收到的item到底是什麼類型的
        if item.__class__.__name__ =='SunlineproItem':#返回當前對象的類名
            print(item['title'],item['netFriend'])
        else:
            print(item['content'])#不是就取出詳情
        return item

settings中打開相關 設置

分佈式爬取

- 概念:能夠將一組程序執行在多臺機器上(分佈式機羣),使其進行數據的分佈爬取。
- 原生的scrapy框架是否能夠實現分佈式?
- 不能夠?
- 調度器不能夠被分佈式機羣共享
- 管道不能夠被分佈式機羣共享
- 藉助scrapy-redis這個模塊幫助scrapy實現分佈式
- scrapy-redis做用:
- 能夠提供能夠被共享的管道和調度器
- pip install scrapy-redis

- 分佈式的實現流程
- 導報:from scrapy_redis.spiders import RedisCrawlSpider
- 修改爬蟲文件的代碼:
- 將當前爬蟲類的父類修改爲RedisCrawlSpider
- 將start_urls刪除
- 添加一個新屬性redis_key = 'ts':能夠被共享的調度器中的隊列名稱

- 設置管道:
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
# 'scrapyRedisPro.pipelines.ScrapyredisproPipeline': 300,
}
- 設置調度器:
# 增長了一個去重容器類的配置, 做用使用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.12.154'
REDIS_PORT = 6379

- 配置redis:
修改Redis的配置文件:redis.windows.conf
- #bind 127.0.0.1
- protected-mode no
- 攜帶配置文件啓動redis服務
- redis-server ./redis.windows.conf
- 啓動redis客戶端

- 執行工程:scrapy runspider xxx.py

- 手動將起始url扔入調度器的隊列中(redis-cli):lpush ts www.xxx.com

- redis-cli: items:xxx

###############

scrapy startproject scrapyRedisPro

scrapy genspider -t crawl test www.x.com

========test.py======

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapyRedisPro.items import ScrapyredisproItem
from scrapy_redis.spiders import RedisCrawlSpider
from scrapy_redis.spiders import RedisSpider

class TestSpider(RedisCrawlSpider):
    name = 'test'
    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']
    redis_key = 'ts' #能夠被共享的調度器中的隊列名稱
    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]/text()').extract_first()
            netFriend = tr.xpath('./td[4]/text()').extract_first()
            item = ScrapyredisproItem()
            item['title'] = title
            item['net'] = netFriend

            yield item
            #提交的item必須保證提交到能夠被共享的管道中

=====items=====

import scrapy


class ScrapyredisproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    net = scrapy.Field()

====settings====

ITEM_PIPELINES = {
   # 'scrapyRedisPro.pipelines.ScrapyredisproPipeline': 300,
    '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_HOST = '127.0.0.1'
REDIS_PORT = 6379

CONCURRENT_REQUESTS = 2#請求進程

D:\老男孩老師筆記\第六階段爬蟲\pachong練習\day07\scrapyRedisPro\scrapyRedisPro\spiders>scrapy runspider test.py #開啓

 

C:\Users\Administrator>redis-cli
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> ^C
C:\Users\Administrator>redis-cli -h 127.0.0.1
127.0.0.1:6379> lpush ts http://wz.sun0769.com/index.php/question/questionType?type=4&page=

127.0.0.1:6379> lrange test:items 0 -1

url增量爬蟲

- 概念:監測網上數據更新的狀況。
- 數據指紋

判斷網站詳情頁的url是否進行請求發送

########dianying.py#############

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import ScrapydianyingItem
from redis import Redis


class DianyingSpider(CrawlSpider):
    name = 'dianying'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567tv.tv/frim/index1.html']
    link = LinkExtractor(allow=r'/frim/index1-\d+.html')
    rules = (
        Rule(link, callback='parse_item', follow=False),#只爬取前幾頁
    )
    conn = Redis(host='127.0.0.1', port=6379)

    # 解析電影的名稱和詳情頁的url
    def parse_item(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/a/@title').extract_first()
            detail_url = 'https://www.4567tv.tv' + li.xpath('./div/a/@href').extract_first()
            item = ScrapydianyingItem()
            item['title'] = title

            # 判斷該詳情頁的url是否進行請求發送
            ex = self.conn.sadd('movie_detail_urls', detail_url)
            if ex == 1:  # 說明detail_url不存在於redis的set中
                print('已有最新數據更新,請爬......')
                yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
            else:
                print('暫無新數據的更新!!!')

    def parse_detail(self, response):
        item = response.meta['item']
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['desc'] = desc

        yield item

########items############

class ScrapydianyingItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title=scrapy.Field()
    desc=scrapy.Field()

###########pipelines##########

class ScrapydianyingPipeline(object):
    def process_item(self, item, spider):
        dic = {
            'title':item['title'],
            'desc':item['desc']
        }
        conn = spider.conn

        conn.lpush('movie_data',dic)
        return item

##客戶端鏈接redis進行驗證

127.0.0.1:6379> keys *
1) "movie_detail_urls"
2) "movie_data"

127.0.0.1:6379> llen movie_data
(integer) 144

127.0.0.1:6379> smembers movie_detail_urls

數據增量爬蟲

- 需求:爬取糗事百科中的段子和做者數據。

#############qiubai.py#############

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis
import hashlib
from ..items import BaikeItem
class QiubaiSpider(CrawlSpider):
    name = 'qiubai'
    # allowed_domains = ['www.baid.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    rules = (
        Rule(LinkExtractor(allow=r'/text/page/\d+/'), callback='parse_item', follow=False),
    )
    # 建立redis連接對象
    conn = Redis(host='127.0.0.1', port=6379)
    def parse_item(self, response):
        div_list = response.xpath('//div[@id="content-left"]/div')
        for div in div_list:
            item = BaikeItem()
            item['author'] = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first()
            item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first()
            # 將解析到的數據值生成一個惟一的標識進行redis存儲
            source = item['author'] + item['content']
            source_id = hashlib.sha256(source.encode()).hexdigest()
            # 將解析內容的惟一表示存儲到redis的data_id中
            ex = self.conn.sadd('data_id', source_id)
            if ex == 1:
                print('該條數據沒有爬取過,能夠爬取......')
                yield item
            else:
                print('該條數據已經爬取過了,不須要再次爬取了!!!')

###########items.py###########

import scrapy


class BaikeItem(scrapy.Item):
    # define the fields for your item here like:
    author=scrapy.Field()
    content=scrapy.Field()

#########pipelines.py#############

class BaikePipeline(object):
    def process_item(self, item, spider):
        dic = {
            'title':item['author'],
            'desc':item['content']
        }
        conn = spider.conn

        conn.lpush('qiubaiData',dic)
        return item

D:\老男孩老師筆記\第六階段爬蟲\pachong練習\day07\baike>scrapy crawl qiubai 執行

相關文章
相關標籤/搜索