1. 程式人生 > 其它 >學習python-爬蟲

學習python-爬蟲

爬蟲04

1.爬蟲介紹

  • python是做爬蟲比較方便,很多爬蟲的庫。其次java、go
  • http協議
    • pc端、小程式、app
  • 模擬傳送http請求,拿到返回資料然後解析出我們想要的資料,最後儲存氣起來。
    • requests,selenium bs4

2.request模組使用

  • 傳送get請求
  • 請求地址中帶資料:直接拼、params引數
  • 編碼問題、url編碼
  • 攜帶請求頭:user-agent、referer
  • 攜帶cookie:登入後cookie、就是登入狀態
    • 帶在請求頭中
    • 帶在cookie引數中:字典、cookiejar物件
  • 傳送post請求
  • 攜帶資料:data、json、傳檔案型別
  • Response物件的屬性和方法
    • text
    • headers
    • cookies
    • content:檔案

3.代理

  • 封ip、代理
  • 代理ip:收費
  • 開源的代理池

4.requests爬取視訊

  • referer,視訊地址還需要轉換

5.bs4解析xml庫

  • html是xml的一種

6.bs4遍歷

  • .
  • 取屬性
  • 取文字:text、string、strings
  • 巢狀使用
  • 父親、兄弟,子孫...

7.bs4搜尋

  • 可以和遍歷連用
  • find、find_all
  • 5種搜尋方式:字串、布林、正則、列表、方法

8.css選擇器

  • 標籤名
  • 類名
  • id

9.selenium

  • 控制瀏覽器、瀏覽器驅動跟瀏覽器版本對應
  • python操作瀏覽器
  • 無頭瀏覽器
  • 等待
  • 搜尋方式:find_element(by='', value='')
    • css選擇器:CSS_SELECTOR
    • xpath
  • 獲取cookie
    • 將cookie儲存到本地
    • 開啟頁面,把cookie寫入

10.抽屜半自動點贊

  • 使用requests傳送點贊請求,需要攜帶cookie
  • 先使用selenium登入進去,然後拿到cookie
  • 最後用requests使用自動點贊。

一、xpath的使用

  • html中選擇標籤可以使用的通用方式有2種:css選擇器和xpath選擇

    XPath即XML路徑語言(XML Path Language),它是一種用來確定XML文件中某部分位置的語言

  • 語法的簡單介紹

    nodename	選取此節點的所有子節點
    / 			從根節點選取	/body/div
    //			從匹配選擇當前節點,而不考慮它們的位置	//div
    .			選取當前節點
    ..			選取當前節點的父節點
    @			選取屬性
    
  • 最簡單的方式copy複製

doc = '''
<html>
 <head>
  <base href='http://example.com/' />
  <title>Example website</title>
 </head>
 <body>
  <div id='images'>
   <a href='image1.html' id='id_a'>Name: My image 1 <br/><img src='image1_thumb.jpg' /></a>
   <a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' /></a>
   <a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' /></a>
   <a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' /></a>
   <a href='image5.html' class='li li-item' name='items'>Name: My image 5 <br /><img src='image5_thumb.jpg' /></a>
   <a href='image6.html' name='items'><span><h5>test</h5></span>Name: My image 6 <br /><img src='image6_thumb.jpg' /></a>
  </div>
 </body>
</html>
'''

from lxml import etree

html = etree.HTML(doc)
# html = etree.parse('search.html', etree.HTMLParser())
# 1.所有節點
a = html.xpath('//*')
# 2.指定節點(結果為列表)
b = html.xpath('//head')
# 3.子節點,子孫節點
c = html.xpath('//div/a')
d = html.xpath('//body/a')  # 子節點沒有a則沒有資料
e = html.xpath('//body//a')
# 4.父節點
aa = html.xpath('//body//a[@href="image1.html"]/..')
bb = html.xpath('//body//a[1]/..')
# 也可以這樣
cc = html.xpath('//body//a[1]/parent::*')
dd = html.xpath('//body//a[1]/parent::div')
# 5.屬性匹配
ee = html.xpath('//body//a[@href="image1.html"]')
# 6.文字獲取 text()
ff = html.xpath('//body//a[@href="image1.html"]/text()')

# 7.屬性獲取 @
g = html.xpath('//body//a/@href')
gg = html.xpath('//body//a/@id')

# 8.屬性多值匹配
# a標籤有多個class類,直接匹配就不行,需要yogacontains
h = html.xpath('//body//a[@class="li"]')
hh = html.xpath('//body//a[@name="items"]')
i = html.xpath('//body//a[contains(@class,"li")]')
ii = html.xpath('//body//a[contains(@class, "li")]/text()')
# 多屬性匹配
j = html.xpath('//body//a[contains(@class, "li")or @name="items"]')
jj = html.xpath('//body//a[contains(@class, "li") and @name="items"]/text()')

#10.按序選擇
k = html.xpath('//a[2]/text()')
kk = html.xpath('//a[3]/@href')
# 取最後一個
l = html.xpath('//a[last()]/@href')
# 位置小於3
ll = html.xpath('//a[position() < 3]/@href')
# 倒數第二個
m = html.xpath('//a[last()-2]/@href')
# 節點軸選擇
# 祖先節點:ancestor
# 使用了* 獲取所有祖先節點
mm = html.xpath('//a/ancestor::*')
# 獲取祖先節點中的div
n = html.xpath('//a/ancestor::div')
# 屬性值:attribute
nn = html.xpath('//a[1]/attribute::*')
o = html.xpath('//a[1]/attribute::href')
# 直接子節點:child
oo = html.xpath('//a[1]/child::*')
# 所有子孫節點:descendant
p = html.xpath('//a[6]/descendant::*')

#當前節點之後所有節點
pp = html.xpath('//a[1]/following::*')
# q = html.xpath('//a[1]following::*[1]/@href')
# 當前節點之後同級節點:following-sibing
qq = html.xpath('//a[1]/following-sibling::*')
s = html.xpath('//a[1]/following-sibling::a')
ss = html.xpath('//a[1]/following-sibling::*[2]')
sss = html.xpath('//a[1]/following-sibling::*[2]/@href')
print(b)


二、selenium動作鏈

  • 網站中有些按住滑鼠,滑動的效果===》滑動驗證碼

  • 兩種形式

    • 形式一:

      actions = ActionChains(bro) # 拿到動作鏈物件
      actions.drag_and_drop(sourse, target) # 把動作放到動作鏈中,準備序列執行
      actions.perform()
      
    • 形式二:

      ActionChains(bro).click_and_hold(sourse).perform()
      distance = target.location['xxx']-sourse.location['xx']
      track = 0
      while track < distance:
          ActionChains(bro).move_by_offset(xoffset=2, yoffset=0).perform()
          track +=2
      
    import target as target
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver import ActionChains
    import time
    
    bro = webdriver.Chrome(executable_path='./chromedriver.exe')
    bro.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
    bro.implicitly_wait(10)
    
    try:
        bro.switch_to.frame('iframeResult') ## 切換到iframeResult
        sourse = bro.find_element(by=By.ID, value='draggable')
        target -= bro.find_element(by=By.ID, value='droppable')
    
        # 方式一:基於同一個動作鏈序列執行
        # actions = ActionChains(bro) # 拿到動作鏈物件
        # actions.drag_and_drop(sourse, target) # 把動作放在動作鏈中,準備序列執行
        # actions.perform()
    
        # 方式二:不同的動作鏈,每次移動的位移都不同
        ActionChains(bro).click_and_hold(sourse).perform()
        distance = target.location['x'] - sourse.location['x']
        print('目標距離源的x軸距離:', distance)
        track = 0
        while track < distance:
            ActionChains(bro).move_by_offset(xoffset=2, yoffset=0).perform()
            track += 2
            ActionChains(bro).release().perform()
            time.sleep(10)
    
    except Exception as e:
        print(e)
    finally:
        bro.close()
    
    

三、自動登入12306

from selenium import webdriver
from selenium.webdriver.common.by import By
import time
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")  # 去掉自動化控制的提示
bro = webdriver.Chrome(executable_path='./chromedriver.exe', options=options)

bro.get('https://kyfw.12306.cn/otn/resources/login.html')
bro.maximize_window()
# 12306檢測到我們使用了selenium控制了瀏覽器,所以它的滑塊出不來
bro.implicitly_wait(10)

try:
    username = bro.find_element(by=By.ID, value='J-userName')
    username.send_keys('13735760305')
    password = bro.find_element(by=By.ID, value='J-password')
    password.send_keys('xcc123')
    time.sleep(3)
    btn = bro.find_element(by=By.ID, value='J-login')
    btn.click()
    span = bro.find_element(by=By.ID, value='nc_1_n1z')

    ActionChains(bro).click_and_hold(span).perform()  # 滑鼠點主
    ActionChains(bro).move_by_offset(xoffset=300, yoffset=0).perform()  # 滑動

    time.sleep(10)

except Exception as e:
    print(e)
finally:
    bro.close()

注意:

* 有的網站可以檢測到使用了自動化控制所以我們需要加一行程式碼:options.add_argument("--disable-blink-features=AutomationControlled") 

四、打碼平臺使用

  • 使用打碼平臺自動登入``
import time

from selenium import webdriver
from selenium.webdriver.common.by import By
from chaojiying import ChaojiyingClient
from PIL import Image
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('http://www.chaojiying.com/apiuser/login/')
bro.implicitly_wait(10)
bro.maximize_window()
try:
    username = bro.find_element(by=By.XPATH, value='/html/body/div[3]/div/div[3]/div[1]/form/p[1]/input')
    password = bro.find_element(by=By.XPATH, value='/html/body/div[3]/div/div[3]/div[1]/form/p[2]/input')
    code = bro.find_element(by=By.XPATH, value='/html/body/div[3]/div/div[3]/div[1]/form/p[3]/input')
    btn = bro.find_element(by=By.XPATH, value='/html/body/div[3]/div/div[3]/div[1]/form/p[4]/input')
    username.send_keys('306334678')
    password.send_keys('lqz123')
    # 獲取驗證碼:
    #1 整個頁面截圖
    bro.save_screenshot('main.png')
    # 2 使用pillow,從整個頁面中截取出驗證碼圖片 code.png
    img = bro.find_element(By.XPATH, '/html/body/div[3]/div/div[3]/div[1]/form/div/img')
    location = img.location
    size = img.size
    print(location)
    print(size)
    # 使用pillow扣除大圖中的驗證碼
    img_tu = (int(location['x']), int(location['y']), int(location['x'] + size['width']), int(location['y'] + size['height']))
    # # 摳出驗證碼
    # #開啟
    img = Image.open('./main.png')
    # 摳圖
    fram = img.crop(img_tu)
    # 截出來的小圖
    fram.save('code.png')
    # 3 使用超級鷹破解
    chaojiying = ChaojiyingClient('306334678', 'lqz123', '937234')  # 使用者中心>>軟體ID 生成一個替換 96001
    im = open('code.png', 'rb').read()  # 本地圖片檔案路徑 來替換 a.jpg 有時WIN系統須要//
    print(chaojiying.PostPic(im, 1902))  # 1902 驗證碼型別  官方網站>>價格體系 3.4+版 print 後要加()
    res_code=chaojiying.PostPic(im, 1902)['pic_str']
    code.send_keys(res_code)
    time.sleep(5)
    btn.click()
    time.sleep(10)
except Exception as e:
    print(e)
finally:
    bro.close()

五、使用selenium爬取京東商品資訊

from selenium import webdriver
from selenium.webdriver.common.by import By  # 按照什麼方式查詢,By.ID,By.CSS_SELECTOR
import time
from selenium.webdriver.common.keys import Keys


def get_goods(driver):
    try:
        goods = driver.find_elements(by=By.CLASS_NAME, value='gl-item')
        for good in goods:
            name = good.find_element(by=By.CSS_SELECTOR, value='.p-name em').text
            price = good.find_element(by=By.CSS_SELECTOR, value='.p-price i').text
            commit = good.find_element(by=By.CSS_SELECTOR, value='.p-commit a').text
            url = good.find_element(by=By.CSS_SELECTOR, value='.p-name a').get_attribute('href')
            img = good.find_element(by=By.CSS_SELECTOR, value='.p-img img').get_attribute('src')
            if not img:
                img ='https://'+ good.find_element(by=By.CSS_SELECTOR, value='.p-img img').get_attribute('data-lazy-img')

            print('''
            商品名字:%s
            商品價格:%s
            商品連結:%s
            商品圖片:%s
            商品評論:%s
            ''' % (name, price, url, img, commit))

        button = driver.find_element(by=By.PARTIAL_LINK_TEXT, value='下一頁')
        button.click()
        time.sleep(1)
        get_goods(driver)
    except Exception as e:
        print(e)


def spider(url, keyword):
    driver = webdriver.Chrome(executable_path='./chromedriver.exe')
    driver.get(url)
    driver.implicitly_wait(10)  # 使用隱式等待
    try:
        input_tag = driver.find_element(by=By.ID, value='key')
        input_tag.send_keys(keyword)
        input_tag.send_keys(Keys.ENTER)
        get_goods(driver)
    finally:
        driver.close()


if __name__ == '__main__':
    spider('https://www.jd.com/', keyword='精品內衣')

六、scrapy介紹

  • 前面學的都是模組如果是做專業的爬蟲需要使用框架,類似於django:web

  • scrapy:爬蟲框架

    • 做爬蟲用的東西都封裝好了,只需要在固定的位置上寫固定的程式碼即可
  • scrapy:號稱爬蟲界的django

    • django 大而全,做web相關的它都用到
    • scrapy 大而全,做爬蟲相關的它都用到
  • 介紹:

    Scrapy一個開源和協作的框架,起初為了頁面的抓取(更準確來說,網路抓取)所設計的,使用它可以快速、簡單、可擴充套件的方式從網站中提取所需的資料。但是目前Scrapy的用途十分廣泛,可用於資料探勘、監測和自動化測試等領域,也可以應用在獲取API所返回的資料或通用的網路爬蟲
    
  • 安裝 scrapy

    * 針對mac、linux:
    	pip3 install scrapy
    * 針對win:可能會出錯(看情況)
    	pip3 install scrapy
        如果安裝失敗可能作業系統缺少一些依賴。
        1.pip3 install wheel # 安裝後,方便通過wheel檔案安裝軟體 xx.whl
        2.pip3 insatll lxml
        3.pip3 install pyopenssl
        4.下載並安裝pywin32:
        https://sourceforge.net/projects/pywin32/files/pywin32/
        5.下載twisted的wheel檔案:
        http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
        6.執行pip3 install 下載目錄\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
        7.pip3 install scrapy
    
  • 釋放出scrapy 可執行檔案

    • 以後使用這個建立爬蟲專案===》django-admin建立django專案
  • 建立爬蟲專案

    • scrapy startproject myfirstscrapy
  • 建立爬蟲【django建立app】

    • scrapy genspider 專案名(爬蟲名字) 爬取的地址
  • 啟動爬蟲

    • scrapy crawl cnblogs --nolog
  • pycharm中執行

    新建run.py
    from scrapy.cmdline import execute
    execute(['scrapy', 'crawl', 'cnblogs','--nolog'])
    

七、scrapy架構介紹

  • 引擎(EGINE)

    引擎負責控制系統所有元件之間的資料流,並在某些動作發生時觸發事件。有關詳細資訊,請參照上面的資料流部分。
    
  • 排程器(SCHEDULER)

    用來接受引擎發過來的請求並壓入佇列中,並在引擎再次請求的時候返回,可以想象成一個URL的優先順序佇列,由它決定下一個要抓取的網址是什麼,同時去除重複的網址
    
  • 下載器(DOWLOADER)

    用於下載網頁內容並將網頁內容返回給EGINE,下載器是建立在twisted這個高效的非同步模型上的
    
  • 爬蟲(SPIDERS)

    SPIDERS是開發人員自定義的類,用來解析responses,並且提取items或者傳送新的請求
    
  • 專案管道(ITEM PIPLINES)

    在item被提取後負責處理他們,主要包括清理、驗證、持久化(比如存到資料庫)等操作
    
  • 下載器中介軟體(DOWLOADER Middlewares)

    位於Scrapy引擎和下載器之間,主要用來處理從EGINE傳到DOWLOADER的請求request,已經從DOWNLOADER傳到EGINE的響應responses,你可以用該中介軟體做一下幾件事情:
    		1.設定請求,
        	2.設定cookie
            3.使用代理
            4.整合selenium
    
  • 爬蟲中介軟體(Spider Middlewares)

    位於EGINE和SPIDERS之間,主要工作是處理SPIDERS的輸入(即responses)和輸出(即requests)
    

八、scrapy解析資料

  • response物件有css方法和xpath方法

    • css中寫css選擇器
    • xpath中寫xpath選擇
  • 重點1

    * xpath取文字內容
    	'.//a[contains(@class,"link-title")]/text()'
    * xpath取屬性
    	 './/a[contains(@class,"link-title")]/@href'
    * css取文字
    	'a.link-title::text'
    * css取屬性
    	'img.image-scale::attr(src)'
    	
    
  • 重點2

    .extract_first()	取一個
    .extract()			取所有
    
import scrapy
from bs4 import BeautifulSoup


class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'
    allowed_domains = ['www.cnblogs.com']
    start_urls = ['http://www.cnblogs.com/']

    def parse(self, response):
        # response類似於requests模組的response物件
        # print(response.text)
        # 返回的資料,解析資料

方式1:使用bs4(不用了)

# 方式一:使用bs4
        soup = BeautifulSoup(response.text, 'lxml')
        article_list = soup.find_all(class_='post-item')
        for article in article_list:
            title_name = article.find(name='a', class_='post-item-title').text
            print(title_name)

方式2:scrapy自帶的解析> css解析

 article_list = response.css('article.post-item')
        for article in article_list:
            title_name = article.css('section>div>a::text').extract_first()
            author_img = article.css('p.post-item-summary>a>img::attr(src)').extract_first()
            desc_list = article.css('p.post-item-summary::text').extract()
            desc = desc_list[0].replace('\n', '').replace(' ', '')
            if not desc:
                desc = desc_list[1].replace('\n', '').replace(' ', '')

            author_name = article.css('section>footer>a>span::text').extract_first()
            article_date = article.css('section>footer>span>span::text').extract_first()
            # 文章詳情內容,因為在下一頁,先不考慮
            print('''
            文章標題:%s
            作者標題:%s
            摘要:%s
            作者名字:%s
            釋出日期:%s
            ''' % (title_name, author_img, desc, author_name, article_date))

方式3:scrapy自帶的解析> xpath解析

article_list = response.xpath('//article[contains(@class,"post-item")]')
        for article in article_list:
            title_name = article.xpath('./section/div/a/text()').extract_first()
            author_img = article.xpath('./section/div/p//img/@src').extract_first()
            desc_list = article.xpath('./section/div/p/text()').extract()
            desc = desc_list[0].replace('\n', '').replace(' ', '')
            if not desc:
                desc = desc_list[1].replace('\n', '').replace(' ', '')
            author_name = article.xpath('./section/footer/a/span/text()').extract_first()
            article_date = article.xpath('./section/footer/span/span/text()').extract_first()
            print('''
                文章標題:%s
                作者標題:%s
                摘要:%s
                作者名字:%s
                釋出日期:%s
            ''' % (title_name, author_img, desc, author_name, article_date))

九、settings相關配置,提高爬取效率

基礎配置

# 爬蟲專案名
BOT_NAME = 'myfirstscrapy'

# 指定爬蟲類的py檔案的位置
SPIDER_MODULES = ['myfirstscrapy.spiders']
NEWSPIDER_MODULE = 'myfirstscrapy.spiders'

# 使用者型別(攜帶在請求頭裡)
USER_AGENT = 'myfirstscrapy (+http://www.yourdomain.com)'

# Obey robots.txt rules==>是否遵循爬蟲協議
ROBOTSTXT_OBEY = True

# LOG_LEVEL 日誌級別
LOG_LEVEL = 'ERROR'  # 報錯如果不列印日誌,在控制檯看不到錯誤

# 啟動執行緒數(多少個爬蟲同時工作)
CONCURRENT_REQUESTS = 32

# Override the default request headers:==>預設請求頭
 DEFAULT_REQUEST_HEADERS = {
   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
   'Accept-Language': 'en',
 }
    
# 爬蟲中介軟體
 SPIDER_MIDDLEWARES = {
    'myfirstscrapy.middlewares.MyfirstscrapySpiderMiddleware': 543,
}    

# 下載中介軟體
 DOWNLOADER_MIDDLEWARES = {
    'myfirstscrapy.middlewares.MyfirstscrapyDownloaderMiddleware': 543,
}
    
# 持久化配置
 ITEM_PIPELINES = {
    'myfirstscrapy.pipelines.MyfirstscrapyPipeline': 300,
 }

增加爬蟲的爬取效率

1.增加併發 預設開啟執行緒數為16
	預設scrapy開啟的併發執行緒為32個,可以適當進行增加。在settings配置檔案中修改
	CONCURRENT_REQUESTS = 100
值為100相當於併發設定成了100.
2.降低日誌級別
	在執行scrapy時會有大量日誌資訊的輸出。為了減少CPU的使用率。可以設定log輸出資訊為INFO或者ERROR即可。在settings配置檔案寫:
	LOG_LEVEL = 'INFO'
3.禁止cookie
	如果不是真的需要cookie,則在scrapy爬取資料時候禁止cookie從而減少CPU的使用率,提高爬取效率。在配置檔案中編寫:
    	COOKIES_ENABLED = False
4.禁止重試
	對失敗的HTTP進行重新請求(重試)會減少爬取速度,因此可以禁止重試。在配置檔案中編寫:
    RETRY_ENABLED = False
5.減少下載超時
	如果對用一個非常慢的連結進行爬取,減少下載超時可以讓卡住的連結快速被放棄,從而提高效率。在配置檔案中降序編寫:
    DOWNLOAD_TIMEOUT = 10s

十、 持久化方案

  • 儲存到硬碟上

  • 兩種方案:第二種常用

    • 第一種:瞭解

      * 解析函式中parse,需要return [{}, {}, {}] # 列表套字典
      * scrapy crawl cnblogs -o 檔名(json, pickle, csv結尾的檔案)
      
    • 第二種:使用pipeline 常用的管道形式,可以同時存多個位置的

      1. 在items.py 中寫一個類[相當於django的表模型],繼承scrapy.Item
      2. 在類中寫屬性,寫欄位,所有欄位都是scrapy.Field型別
      	eg:	title = scrapy.Field()
      3. 在爬蟲中匯入類,例項化得到物件,把要儲存的資料放到物件中
      	item['title'] = title  【注意:不能用 . 的形式去獲取】
          解析類中 yield item
      4. 修改配置檔案,指定pipline,數字表示優先順序,越小則優先順序越大
      	ITEM_PIPELINES = {
              'crawl_cnblogs.pipelines.CrawlCnblogsPipeline': 300,        
          }
      5. 寫一個pipline:CrawlCnblogsPipeline
      	* open_spider:資料初始化,開啟檔案,開啟資料庫連結
          * process_item:真正儲存的地方
          注意: 最後需要return item,交給後續pipeline繼續使用
          * close——spider:銷燬資源,關閉檔案,關閉資料庫連結
      

十一、全站爬取cnblogs文章

  • 第一頁爬完後,要儲存的資料已經儲存
  • 之後需要歐兩件事情:
    1. 繼續爬取下一頁資料===>解析洗衣液的地址,包裝成request物件
    2. 繼續爬取詳情頁===>解析出詳情頁的地址,包裝成request物件
  • 現在在這裡不能儲存,因為資料不全,缺少文章詳情,我們需要加上文章詳情後再一次性儲存。

request和response物件傳遞引數

  • Request建立

    • 在parse中,for迴圈中,建立Request物件時,傳入meta

      yield Request(url=url, callback=self.detail_parse,meta={'item':item})

  • Response物件

    • detail_parse中,通過response取出meta再取出meta裡面的item,把文章詳情寫入。

      yield item

解析下一頁並繼續爬取

spiders/cnblogs.py
from scrapy import Request
from myfirstscrapy.items import CnblogsItem
import scrapy
from bs4 import BeautifulSoup


class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'
    allowed_domains = ['www.cnblogs.com']
    start_urls = ['http://www.cnblogs.com/']

    def parse(self, response):
        # item = CnblogsItem()  # 外面定義,會有問題
        article_list = response.xpath('//article[contains(@class,"post-item")]')
        for article in article_list:
            item = CnblogsItem()  # 定義在for內部,每次都是一個新物件
            title_name = article.xpath('./section/div/a/text()').extract_first()
            author_img = article.xpath('./section/div/p//img/@src').extract_first()
            desc_list = article.xpath('./section/div/p/text()').extract()
            desc = desc_list[0].replace('\n', '').replace(' ', '')
            if not desc:
                desc = desc_list[1].replace('\n', '').replace(' ', '')
            author_name = article.xpath('./section/footer/a/span/text()').extract_first()
            article_date = article.xpath('./section/footer/span/span/text()').extract_first()
            url = article.xpath('./section/div/a/@href').extract_first()

            item['title_name'] = title_name
            item['author_img'] = author_img
            item['desc'] = desc
            item['author_name'] = author_name
            item['article_date'] = article_date
            item['url'] = url
            # print(url)
            # 現在不存了,因為資料不全,等全了以後再存,繼續爬取,就要建立Request物件
            # 詳情頁面,使用self.detail_parse解析
            yield Request(url=url, callback=self.detail_parse, meta={'item': item})

        # 解析出下一頁地址
        # css
        next_url = 'https://www.cnblogs.com' + response.css('div.pager>a:last-child::attr(href)').extract_first()
        print(next_url)
        yield Request(url=next_url, callback=self.parse)

    def detail_parse(self, response):
        # print(len(response.text))
        item = response.meta.get('item')
        # 解析詳情
        article_content = response.css('div.post').extract_first()
        # print(article_content)
        # print('===================')
        # 把詳情,寫入當前meta中得item中
        item['article_content'] = str(article_content)
        yield item

items.py
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class CnblogsItem(scrapy.Item):
    # # define the fields for your item here like:
    # # name = scrapy.Field()
    # pass
    title_name = scrapy.Field()
    author_img = scrapy.Field()
    desc = scrapy.Field()
    author_name = scrapy.Field()
    article_date = scrapy.Field()
    url = scrapy.Field()
    article_content = scrapy.Field() # 文章詳情
pipelines.py
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
import pymysql
from itemadapter import ItemAdapter


class CnblogsMysqlPipeline:
    def open_spider(self, spider):
        # 開啟資料庫連結
        self.conn = pymysql.connect(
            user='root',
            password='123',
            host='127.0.0.1',
            database='cnblogs',
            port=3306,
            autocommit=True  # 自動提交,配置後就不用在下面新增 self.conn.commit()
        )
        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        self.cursor.execute(
              'insert into artice (title_name,author_img,`desc`,author_name,article_date,url,article_content) values (%s,%s,%s,%s,%s,%s,%s)',
            args=[item['title_name'], item['author_img'], item['desc'], item['author_name'], item['article_date'],
                  item['url'], item['article_content']])
        # self.conn.commit() # 提交
        return item

    # 關閉資料庫
    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()


class CnblogsFilesPipeline:
    def open_spider(self, spider):
        # cnblogs這個爬蟲物件
        self.f = open('cnblogs.txt', 'at', encoding='utf-8')

    def process_item(self, item, spider):
        # 存真正的資料,每次item都會走一次
        print(item)
        # 存檔案不用這種方式,最好爬蟲啟動,把檔案開啟爬蟲結束檔案就關閉了
        with open('cnblogs.txt', 'at', encoding='utf-8') as f:
            f.write('文章標題:%s, 文章作者:%s\n' % (item['title_name'], item['author_name']))
            # return item

        self.f.write('文章標題:%s, 文章作者:%s\n' % (item['title_name'], item['author_name']))
        return item

    def close_spider(self, spider):
        print('我關了')
        self.f.close()

十二、爬蟲和下載中介軟體

  • scrapy的所有中介軟體都寫在middlewares.py中,跟django非常像,做一些攔截。

  • 爬蟲中介軟體(用的很少,瞭解即可)

    	MyfirstscrapySpiderMiddleware
            def process_spider_input(self, response, spider): # 進入爬蟲會執行它
            def process_spider_output(self, response, result, spider): #從爬蟲出來會執行它
            def process_spider_exception(self, response, exception, spider):#出了異常會執行
            def process_start_requests(self, start_requests, spider):#第一次爬取執行
            def spider_opened(self, spider): #爬蟲開啟執行
    # 下載中介軟體
    	MyfirstscrapyDownloaderMiddleware
            def process_request(self, request, spider): # request物件從引擎進入到下載器會執行
        	def process_response(self, request, response, spider):# response物件從下載器進入到引擎會執行
        	def process_exception(self, request, exception, spider):#出異常執行它
        	def spider_opened(self, spider): #爬蟲開啟執行它
    
    重點:process_request, process_response
    下載中介軟體的process_request
    * 返回值
    	* return None:繼續執行下面的中介軟體的process_request
    	* return a Response object:不進行下載中介軟體,直接返回給引擎,引擎把它給爬蟲
        * return a Request object:不進入下載中介軟體,直接返回給引擎,引擎把它放到排程器中。
        * raise IgnoreRequest:process_exception() 拋異常,會執行process_exception
    
    下載中介軟體的process_response
    * 返回值
    	* return a Response object:正常,會進入到引擎,引擎會把它給爬蟲
        * return a Request object:會進入到引擎,引擎會把它放進排程器中, 等待下次被爬
        * raise IgnoreRequest     會執行process_exception