scrapy

步驟:
 
目標文件夾下,按Shift+右鍵進控制檯:(Win10則在scrapy各命令前加個python -m )
①scrapy startproject project;執行兩句提示,要改name(別和項目同名;另外項目名及爬蟲名都不要用test、fang等有莫名意義的單詞)和domain(有的不加www.);
②各主要的.py文件:手建的main、items、spiders下的name、pipelines、settings;
③scrapy crawl name -s JOBDIR=job666(或不加此-s命令,改成在settings中加1行:JOBDIR='job666')。在項目根目錄下執行,即scrapy.cfg所在的那層文件夾。
*******************分割線*******************
雜項:
 
步驟①中所提示的scrapy genspider name命令,name前缺省的是 -t basic ,若用CrawlSpider類的rule正則過濾全站網址則改用 -t crawl 。爬xml網頁也可用 -t xmlfeed ,爬表格也可用 -t csvfeed 。
 
③用的-s命令settings:9個請求就停-s CLOSESPIDER_PAGECOUNT=9;CLOSESPIDER_ITEMCOUNT。
而參數JOBDIR,可以使cmd下按1次Ctrl+C以暫停,下次斷點續爬再執行此命令,而連按2次是徹底終止。按了1次Ctrl+C後,有的網站還持續爬個五六秒,靜等而不要按第2次。
另外,有的網站如拉勾,爬蟲要Cookie驗證,而且Cookie中的個別行爲分析key還有有效期,或許會在爬着爬着或下次斷點續爬時過時了。
 
cmd中的各命令也可在PyCharm的Terminal中執行。若 選擇os.system(*)運行,按鈕Pause雙豎線只是暫停了Run窗口的輸出,是假的interrupt,和Terminal窗口下按1次Ctrl+C並不一樣;Stop紅方塊等同7秒內按了2次Ctrl+C,job666下的requests.queue文件夾會被清空,下次運行就報錯no more duplicates will be shown,只能刪除job666後再從頭爬。
而cmd或Terminal卡死了、不慎關了反倒無妨,等同1次Ctrl+C,重啓執行命令依然可斷點續爬。
*******************分割線*******************
spiders文件夾下的主程序.py
 
key=scrapy.Field(),key要經過鍵即item['key']操做(item=…Item()),不支持item.key的屬性訪問。
 
Filtered offsite request to 'url':和allowed_domains衝突。改allowed_domains,或在yield此url句加dont_filter=True。
純url在fiddler能請求,yield Request它就卡住:設了代理且過時了,設DOWNLOAD_TIMEOUT = 3可在3秒後放棄,解放能力去處理其餘url。
 
Request的2類參數:請求のurl,method,body,headers,cookies;回調のcallback,meta,dont_filter。
response的屬性:body、meta['*']、url、urljoin(*)、status、request.headers或headers.getlist('Set-Cookie')[num].decode()。
各scrapy.Request或.FormRequest,在自身及其callback函數間才相似requests.Session(),而在同一個函數內卻相似requests。故各請求間若要傳遞cookies,每一個請求都要獨立寫個函數,而不能擠在一個函數內。
 
如start_urls般寫爲Spider類的類屬性,以自定義更高優先級的settings:
custom_settings=dict(ROBOTSTXT_OBEY=False,DOWNLOAD_DELAY=random.random()/5,
    DEFAULT_REQUEST_HEADERS={'User-Agent':UserAgent().random})
 
重寫start_requests函數,則類屬性start_urls裏的各url再也不被請求。如:
def start_requests(self):
    with open('E:/data.txt',encoding='utf8') as f:
        rows=f.readlines()
    for r in rows:
        url,id=r.replace('\n','').split(';')
        yield Request(url,self.parse_xxx,meta={'id':id})
*******************分割線*******************
LinkExtractor:
 
from scrapy.linkextractors import LinkExtractor
①目標url是src屬性的值,則改參數attrs的默認值爲('href','src');②匹配到的連接用js寫的,如a href = "javascript: ggg('http://……/p/'); return false">,則設參數process_value=self.pv,內容以下:
import re
def pv(value):
    m=re.findall("http.+?p/",value)
    if m:return m[0]
*******************分割線*******************
標籤訂位&內容提取:
 
def parse…(self,response):
parse()的2參response,css、xpath中,在定位標籤後便可提取文本屬性,並沒把定位和提取隔開寫。不提取則返回標籤的list,各標籤可繼續用它倆。最後的.extract()[0],返回str等基本類型,等同.extract_first()。
 
css&xpath可混用:response.xpath('//div[contains(@alt, "→")]').css('.a ::attr(src)').extract()。
xpath在定位後的提取用//text()和//@alt,css是 ::text和 ::attr(alt)。
 
抓取xml,定位無返回結果:response.selector.remove_namespaces(); 再定位如response.css('a')。
css等定位處報錯Response no attribute body_as_unicode,因網站的headers無content-type字段,scrapy不知所抓網頁的類型:把此處的response.text改成response.body。
*******************分割線*******************
scrapy shell url:等效瀏覽器,無需scrapy.Request(url,callback,headers=xx)般模擬頭。
 
①提取大淘客領券下的?條/頁:
scrapy shell www.dataoke.com/qlist/
response.css('b+ b::text').extract_first()
 
②不用css、xpath而是用re,匹配本地1個html(編碼gb2312)中的連接文字:
python -m scrapy shell E:/index之日記本.html
response=response.replace(encoding='gb2312')  #response.encoding
from scrapy.linkextractors import LinkExtractor
LinkExtractor(('Diary\?id=\w+',)).extract_links(response)[0].text  #.url
*******************分割線*******************
scrapy默認的去重指紋是sha1(method+url+body+header),判重指紋偏多致過濾的url少,若有時間戳的,網址改了但網頁沒變依然會重複請求。這時得自定義判重規則,如只根據去除了時間戳的url判重。
 
settings所在目錄新建個內容以下的customDupefilter.py,並在settings中寫入DUPEFILTER_CLASS='項目名.customDupefilter.UrlDupeFilter':
 
import re
from scrapy.dupefilter import RFPDupeFilter
 
class UrlDupeFilter(RFPDupeFilter):
    def __init__(self,path=None):
        self.urls_seen=set()
        RFPDupeFilter.__init__(self,path)
 
    def request_seen(self,request):
        pattern=re.compile('&?time=\d{10}&?')
        url=pattern.sub('&',request.url)
        if not url in self.urls_seen:
            self.urls_seen.add(url)
****************************************分割線****************************************
gerapy:
本機pip install gerapy
 
前提:本機及各分機都需pip install scrapyd,並在cmd執行scrapyd,瀏覽器打開http://127.0.0.1:6800/(雲服務器是ip:6800,要先改*scrapyd.conf文件的bind_address = 0.0.0.0),看看是否都能正常啓動。
 
⒈cmd切至目標目錄:gerapy init→cd gerapy→gerapy migrate→gerapy runserver(雲要補個 0.0.0.0:8000);
⒉單機能正常運行的scrapy項目複製到gerapy\projects下;
⒊瀏覽器打開http://127.0.0.1:8000(雲是ip:8000):①主機管理:建立→name、本機是127.0.0.1、6800、雲的認證、建立;②項目管理:目標scrapy項目行尾的部署→輸入打包描述並打包→勾上覆選框點擊批量部署;③返回主機管理:調度→運行。
*******************分割線*******************
scrapy_redis:
 
settings.py:
REDIS_HOST及REDIS_PORT,爲Redis所在電腦即主機的IP、端口,主機可不寫而各輔機必須寫;
SCHEDULER_PERSIST=True等同給JOBDIR賦值,不刪爬過的urls以供續爬時作去重判斷;
Redis中的key若爲set類型,則加REDIS_START_URLS_AS_SET=True,list等其餘類型保持默認值;
調度SCHEDULER和去重DUPEFILTER_CLASS,由scrapy家的改成redis家的。
 
爬蟲.py:
默認繼承的父類scrapy.Spider改成RedisSpider,整站爬的CrawlSpider則改成RedisCrawlSpider;
start_urls行換爲值隨意如tempStartUrls的類變量redis_key,爬蟲啓動後若redis中無此key則一直監聽等待。
 
redis的客戶端redis-cli.exe裏的經常使用命令:
flushdb:清空數據庫緩存以方便從頭開爬,若之後還要斷點續爬則不要用。
rpush name url:list類型的key的插值,Python中寫爲redis.Redis('主機的ip',6379).rpush(key,*urlList),反義詞lpush在左側插入;key若爲set型則用.sadd(key,*urlList),有序的zset型用.zadd(key,**urlDict)。
 
啓動redis爬蟲:雙擊(若要加載.conf文件中所配置的密碼則經過命令行)以啓動redis的服務端redis-server.exe,若存儲用的mongodb則要net start mongodb;執行main.py中的scrapy crawl xxx。
 
redis配置密碼:
①redis.windows.conf文件(Linux叫redis.conf),在# requirepass foobared下加1行requirepass 123456。
②在redis的安裝目錄下進入命令窗口→redis-server redis.windows.conf。在爬蟲等各任務結束前,此命令窗口要一直開着。若沒出現木桶圖案,則進任務管理器把redis-server.exe結束任務後再執行②。
③打開RDM可視化軟件→Connect …→name隨意,host爲127.0.0.1,port爲6379,auth爲123456→Test …→OK。
*******************分割線*******************
用scrapyd部署爬蟲到阿里雲等服務器:
 
①服務器後臺的入方向選項卡,克隆個默認的受權策略,端口範圍改成6800/6800,受權對象0.0.0.0/0等都不變。  
②在最新版PyCharm建立的虛擬環境中(下文各命令都是在此虛擬環境的Terminal中執行):pip isntall scrapyd→scrapyd。 
③瀏覽器訪問服務器外網ip:6800,發現打不開:法1是改scrapyd包下的default_scrapyd.conf文件的bind_address爲0.0.0.0;但scrapyd沒提供http驗證登陸,故法2不改bind_address,而是把scrapyd掛到反向代理服務器nginx上:http://wsxiangchen.com/details/?id=14。
④修改scrapy項目根下的scrapy.cfg:[deploy]改成[deploy:隨意好比spider名],url的localhost改成外網ip,project名不動,末尾加兩行username=user和password=passwd(user和passwd是在③的nginx中設的);上傳項目到服務器。 
⑤部署爬蟲到服務器:點擊Terminal左上的綠+再新開個窗口→路徑切到項目根(⑥亦在此路徑下)→scrapyd-deploy spider名 -p project名。
⑥運行爬蟲:curl http://外網ip:6800/schedule.json -u username:password -d project=project名 -d spider=spider名。開爬後,可經過ip:6800/Jobs及logs查看運行狀況。
****************************************分割線****************************************
Egの校花網的圖,用傳統的open法下載:
 
一、在目標文件夾校花網下進入控制檯,依次執行以下3條語句:
scrapy startproject xiaohuar;cd xiaohuar;scrapy genspider xhr www.xiaohuar.com
 
二、spiders目錄下的xhr.py:
 
import scrapy,re,os
 
pattern=re.compile('alt="(.+?)"\s+?src="(/d/file/.+?)"')
noName=r'[\\/:*?"<>|]'
 
folder='E:/xiaohuar'
if not os.path.isdir(folder):os.makedirs(folder)
 
class XhrSpider(scrapy.Spider):
    name = 'xhr'
    allowed_domains = ['www.xiaohuar.com']
    spideredCount = 0
 
    # 啓動爬蟲時才接收的參數end,要寫爲類的__init__()形參;self.end可在別的類函數使用
    def __init__(self, end=None, *args, **kwargs):
        super(XhrSpider, self).__init__(*args, **kwargs)
        self.start_urls=[f'http://www.xiaohuar.com/list-1-{x}.html' for x in range(0,int(end))]
        self.end=int(end)
 
    def parse(self, response):
            result = pattern.findall(response.text)
            for name,imgUrl in result:
                if not '.php' in imgUrl:    #有的圖已掛;用response.urljoin(imgUrl)拼接域名和imgUrl
                self.spideredCount += 1
                if self.spideredCount>self.end: break   #循環內用exit()的效果不理想
                yield scrapy.Request(response.urljoin(imgUrl),self.downLoad,meta={'name':name})
 
    def downLoad(self, response):
        fileName=response.meta['name']+'.'+response.url.split('.')[-1]
        fileName=re.sub(noName, ' ', fileName).strip() # 英文標點換爲空格,首尾的不要
        with open(f'E:/xiaohuar/{fileName}', 'wb') as f:
            f.write(response.body)
 
#運行方法①:不建立工程,單文件運行1個scrapy的Spider
if __name__ == '__main__':
    from fake_useragent import UserAgent
    from scrapy.crawler import CrawlerProcess
    from scrapy.utils.project import get_project_settings    #導入本項目的settings
 
    #settings=dict({'User-Agent':UserAgent().random},ROBOTSTXT_OBEY=False)
    settings=get_project_settings()
    process=CrawlerProcess(settings=settings)
    process.crawl(XhrSpider,end=5)  #process.crawl(S2)……添加幾個Spider類就寫幾句crawl
    process.start()
****運行方法①&②的分割線****
#運行方法②:
在cmd或Terminal斷點續爬:scrapy crawl xhr -a end=5 -s JOBDIR=jobXiaohuar。
或在PyCharm中運行:項目根目錄或子目錄下,建個項目的運行文件main.py——
import os    #PyCharm中的輸出若在Run窗口而非Terminal,則不加斷點續爬參數-s
os.system('scrapy crawl xh')
*****注意事項*****
①PyCharm下運行main.py時若報錯"Unknown command: crawl":Run→Edit Configurations→雙擊打開左欄的main→Working Directory設置爲項目根目錄。
 
②輸出結果若導出爲csv(或json),則在python -m  scrapy crawl xxx後加參數: -o "file://E:/xiaohuar.csv" -s FEED_EXPORT_ENCODING=gbk',也可改成在settings中加1句FEED_EXPORT_ENCODING='gbk'。如有特殊字符,則輸出爲csv仍是得用默認的utf8編碼,最後另存爲utf8+BOM就可用excel正常打開。
 
scrapy導出到csv,各行爬取數據間有空行:*packages\scrapy\exporters.py,類CsvItemExporter的__init__函數內,在file,行和line_buffering*行之間加一句【newline='',】(注意有英文逗號)。
****************************************分割線****************************************
Egの爬大淘客各物品的名稱、佣金、銷量、存量、券後價,存至csv:
 
一、scrapy startproject dataoke;
cd dataoke;scrapy genspider dtk www.dataoke.com
 
main.py:只有它一個py文件是手動建立的,放在項目根目錄或子目錄都可
import os    #PyCharm中的輸出若在Run窗口而非Terminal,則不加斷點續爬參數-s
os.system(' scrapy crawl dtk -o "file://E:/da tao ke.csv" -s FEED_EXPORT_ENCODING=gbk')
*******************分割線*******************
二、items.py:設置要爬的字段——
 
name = scrapy.Field()
surplus = scrapy.Field()
currentSales = scrapy.Field()
commission = scrapy.Field()
price = scrapy.Field()
*******************分割線*******************
三、dtk.py:
 
import scrapy
from ..items import DataokeItem  #..items等同dataoke.items
#此處名item,則pipelines中也得叫item;yield item在Request前,可把item句寫在類外
item=DataokeItem()
from fake_useragent import UserAgent
 
class DtkSpider(scrapy.Spider):
    name = 'dtk'
    allowed_domains = ['www.dataoke.com']
    start_urls = [f'http://www.dataoke.com/quan_list?page={n}' for n in range(1,12)]
 
    def start_requests(self):
        for url in self.start_urls:
            #token=hashlib.md5(*random*.encode()).hexdigest(),過時後它倆同時改成最新的
            h = {'Cookie':'token=3c6deb668cfcdf659d52dadd17eb281a;random=2834', \
                'User-Agent':UserAgent().random,'Referer': url}  #取注settings內的COOKIES…
            yield scrapy.Request(url,self.parse,headers=h)
 
    def parse(self, response):
        for x in response.css('div.quan_goods'):
            item['name']=x.css('.quan_title::text').extract_first().strip()
            item['surplus']=x.css('[style*=f15482]::text').extract()[1] #屬性值無0等特殊字符可不套"
            item['currentSales']=x.css('[style*="7DB0D0"]::text').extract_first()
            item['commission']=x.css('[style*="20px"]::text').extract_first().strip().replace(' ','')
            item['price']=x.css('[style*="30px"]::text').extract_first().strip()
            yield item
*******************分割線*******************
五、settings.py:
 
import random
 
ROBOTSTXT_OBEY=False    #改:True爲False;
CONCURRENT_REQUESTS=32    #取注並改:並行請求數32爲64
REACTOR_THREADPOOL_MAXSIZE=20    #增:最大線程數
LOG_LEVEL = 'INFO'    #增:默認的DEBUG適於開發時,生產環境降級log可降耗CUP
REDIRECT_ENABLED=False    #增:禁止重定向
RETRY_TIMES=3    #增:重複請求次數,RETRY_ENABLED=False則禁止重試
DOWNLOAD_TIMEOUT=4    #增:4秒後還沒反應則放棄對它的請求
DOWNLOAD_DELAY=random.random()*5    #取注並改:這網站加載慢,延時設久點
COOKIES_ENABLED=False    #取注:本例的scrapy.Request()請求頭要用獨立的Cookie
 
#JOBDIR='jobDaTaoKe'    #若選擇Terminal運行可啓用本句,Ctrl+C可斷點續爬
****************************************分割線****************************************
Egの無圖小說網,登陸後爬個人書架中的全部藏書:
 
一、python -m scrapy startproject wtxs;
cd wtxs;python -m scrapy genspider -t crawl wutuxs www.wutuxs.com
 
main.py:
import os
os.system('python -m scrapy crawl wutuxs')
*******************分割線*******************
三、wutuxs.py:
 
Rule中各參數的執行順序:start_url的response被link_extractor→所提取的urls被process_request →各urls的response被callback做自定義parse→各urls的response繼續被follow至link_extractor……
 
本例中,start_requests函數傳出登陸後的session,而start_url正是在其回調函數中被請求, 若需改頭亦在此回調內寫;起始url的response在被默認完整parse後傳至Rule,開啓新徵程: 
先被LE參數提取所需的urls;若這些urls須要各異的Referer等等隨後才能被正確請求,則在 process_request指定的函數中設置,並返回改頭換面了的新請求;各完整response傳至callback做自定義的parse。 
若response也要被追蹤,則置follow爲True,重走一遍LE→callback的整個流程。
 
LinkExtractor如有參數指定了函數,正常寫爲self.func,同scrapy.Request的callback參數沒不一樣;就是Rule的參數,如經常使用的callback,和process_request等,寫爲str如'func'。
 
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider,Rule
from fake_useragent import UserAgent
import random
 
class WutuxsSpider(CrawlSpider):
    name='wutuxs'
    allowed_domains=['www.wutuxs.com']
    custom_settings=dict(ROBOTSTXT_OBEY=False,DOWNLOAD_DELAY=random.random()/10,
        DEFAULT_REQUEST_HEADERS={'User-Agent':UserAgent().random})
    rules=(Rule(LinkExtractor(allow='indexflag=1'),
        process_request='addReferer',callback='parseNovel',follow=True),)
 
    def start_requests(self):
        loginUrl='http://www.wutuxs.com/login.php?do=submit'
        h={'Content-Type':'application/x-www-form-urlencoded'}
        fd=dict(username='tom_cheng',password='***',
             usecookie='315360000',action='login',submit=' 登  錄 ')
        yield scrapy.FormRequest(loginUrl,headers=h,formdata=fd,callback=self.afterLogin)
 
    def afterLogin(self, response):
        startUrl='http://www.wutuxs.com/modules/article/bookcase.php'
        yield scrapy.Request(startUrl)
 
    def addReferer(self,request):  #此站無需模擬動態referer,只是找不到好例子
        print(request.headers)  #{},設置cookies等用r*.replace,同response.encoding
        request=request.replace(headers={'User-Agent':UserAgent().random,
            'Content-Type':'application/x-www-form-urlencoded',
            'Referer':'&'.join(request.url.split('&')[:2])})
        return request
 
    def parseNovel(self, response):
        response=response.replace(encoding='gbk')
        chapters=response.css('#at a')
        with open('E:/個人書架.txt','a+',encoding='gbk') as f:
            for chapter in chapters:
                chapterTitle=chapter.css('::text').extract_first()
                chapterUrl=response.urljoin(chapter.css('::attr(href)').extract_first())
                f.write(chapterTitle+':'+chapterUrl+'\n')
*******************分割線*******************
Egの用scrapy.Selector來解析網頁源代碼:
 
import requests
from scrapy import Selector
 
def novelChapters():
    indexUrl='http://www.wutuxs.com'
    html=requests.get(indexUrl+'/modules/article/reader.php?aid=6301')
    html.encoding='gbk'
    chapters=Selector(html).css('#at a')
    print('總章節數:'+str(len(chapters)))
    with open('E:/個人書架.txt','a+') as f:
        for chapter in chapters:
            chapterTitle=chapter.css('::text').extract_first()
            chapterUrl=indexUrl+chapter.css('::attr(href)').extract_first()
            f.write(chapterTitle+':'+chapterUrl+'\n')
 
novelChapters()
****************************************分割線****************************************
Egの豆瓣影評,瀏覽器登陸獲取cookies後,用CrawlSpider爬:
 
一、python -m scrapy startproject doubanMovie;
cd doubanMovie;scrapy genspider comments movie.douban.com
 
main.py:
import os   #有的user或content含特殊字符故不輸出爲gbk,另存爲utf8+BOM
os.system('python -m scrapy crawl comments -o "file://E:/dbMovie.csv"')
*******************分割線*******************
二、items.py:
import scrapy
 
class DoubanmovieItem(scrapy.Item):
    user=scrapy.Field()
    userlink=scrapy.Field()
    view=scrapy.Field()
    rate=scrapy.Field()
    time=scrapy.Field()
    votes=scrapy.Field()
    content=scrapy.Field()
*******************分割線*******************
三、comments.py:
CrawlSpider下,start_requests()不論深層調用了幾個yield Request(),最後那個不能用callback,不然會與Rule()脫鉤;而parse_start_url()有途徑,讓末Request()的url也用上Rule()的callback。
 
import random,string
from selenium import webdriver
from fake_useragent import UserAgent as ua
from scrapy import Request
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider,Rule
from ..items import DoubanmovieItem
 
userName='904477955@qq.com'
pwd='***'
 
def loginByBrowser():
    options=webdriver.ChromeOptions()
    options.binary_location='D:/Program Files/Browser/CentBrowser/Application/chrome.exe'
    options.add_argument('disable-infobars')
    driver=webdriver.Chrome('C:/Program Files/Python36/chromedriver',0,options)
    driver.get('https://www.douban.com/accounts/login?source=movie')
    css=driver.find_element_by_css_selector
    css('#email').send_keys(userName)
    css('#password').send_keys(pwd)
    css('.btn-submit').click()
    try:    #登陸界面的3種狀況:有驗證碼,點擊登陸後跳出驗證碼,無需驗證碼
        if css('#captcha_field'):   #無此標籤:selenium報錯,scrapy用extract_first()不報
            input('瀏覽器端手動輸完驗證碼並點擊登陸後,在本句句尾任敲一字母:')
    except:pass
    cookies={d['name']:d['value'].replace('"','') for d in driver.get_cookies()}
    driver.quit()
    return cookies
 
cookies=loginByBrowser()
#cookies={'ue':'用戶名','__yadk_uid':'密碼加鹽的MD5','bid':'11位隨機str'}
 
class CommentsSpider(CrawlSpider):
    name='comments'
    allowed_domains=['movie.douban.com']
    rules=(Rule(LinkExtractor(restrict_css='.next'),follow=True,
        process_request='update_cookies',callback='parse_item'),)
 
    def start_requests(self):
        firstPage='https://movie.douban.com/subject/20495023/comments?start=0'
        headers={'User-Agent':ua().random}
        #settings保持#COOKIES…行,則cookies用過1次,會共享給後面的請求自動引用
        yield Request(firstPage,headers=headers,cookies=cookies)
 
    def parse_start_url(self,response):
        return self.parse_item(response)
 
    def update_cookies(self,request):
        global cookies
        cookies['bid']=''.join(random.sample(string.ascii_letters+string.digits,11))
        request=request.replace(cookies=cookies,headers={'User-Agent':ua().random})
        return request
 
    def parse_item(self,response):
        item=DoubanmovieItem()
        for x in response.css('.comment'):
            item['user']=x.css('.comment-info> a::text').extract_first()
            item['userlink']=x.css('.comment-info> a::attr(href)').extract_first()
            item['view']=x.css('.comment-info span::text').extract_first()
            item['rate']=x.css('.rating::attr(title)').extract_first('無')
            item['time']=x.css('.comment-time::text').extract_first().strip()
            item['votes']=x.css('.votes::text').extract_first()
            item['content']=x.css('p::text').extract_first().strip()
            yield item
*******************分割線*******************
五、settings.py:
 
import random
from fake_useragent import UserAgent
ROBOTSTXT_OBEY=False    #改
CONCURRENT_REQUESTS=64    #取注&改
REACTOR_THREADPOOL_MAXSIZE=20    #增
RETRY_TIMES=3    #增
DOWNLOAD_TIMEOUT=4    #增
DOWNLOAD_DELAY=random.random()/5    #取注&改
DEFAULT_REQUEST_HEADERS={'User-Agent':UserAgent().random}    #取注&改
****************************************分割線****************************************
Egの豆瓣9分榜單,用RedisSpider爬,存至Excel或MongoDB:
 
一、scrapy startproject doubanBook;
cd doubanBook;scrapy genspider dbbook www.douban.com
 
main.py:
import os
os.system('scrapy crawl dbbook')
*******************分割線*******************
二、items.py:
 
    bookName = scrapy.Field()
    rate = scrapy.Field()
    author = scrapy.Field()
    url=scrapy.Field()
*******************分割線*******************
三、dbbook.py:
本例選用Redis的db3而非默認的db0;redis-server.exe本次也沒經過雙擊啓動,而是執行帶密碼的命令行redis-server redis.windows.conf,conf文件中已新加了一行requirepass 123456。
 
from redis import Redis;r=Redis('127.0.0.1',6379,3,'123456')
from ..items import DoubanbookItem;item=DoubanbookItem()
from scrapy_redis.spiders import RedisSpider
 
class DbbookSpider(RedisSpider):
    name='dbbook'
    allowed_domains=['www.douban.com']
    r.flushdb()    #清空當前所選用的db3
    redis_key='tempStartUrls'
    urls=[f'https://www.douban.com/doulist/1264675/?start={x*25}' for x in range(2)]
    r.sadd(redis_key,*urls)    #.sadd()把redis_key初始爲set對象,.rpush則是list對象
    #exit()  #另選它庫及輸密碼,不只Redis對象要加,scrapy的settings也要有
    custom_settings=dict(REDIS_PARAMS={'db':3,'password':'123456'})
 
    def parse(self, response):
        books=response.css('.bd.doulist-subject')
        for book in books:
            item['bookName']=book.css('.title a::text').extract_first().strip()
            item['rate']=book.css('.rating_nums::text').extract_first()
            item['author']=book.css('.abstract::text').extract_first().strip()
            item['url']=book.css('.title a::attr(href)').extract_first()
            yield item
*******************分割線*******************
四、pipelines.py:
 
# 存儲之法1:存至excel
# from openpyxl import Workbook
 
# class DoubanbookPipeline(object):
    # wb = Workbook()
    # ws = wb.active
    # ws.append(['書名','評分','做者','網址'])
    #
    # def process_item(self, item, spider):
    #     row = [item['bookName'],item['rate'],item['author'],item['url']]
    #     self.ws.append(row)
    #     self.wb.save('E:\douban.xlsx')
    #     return item
 
# 存儲之法2:存至MongoDB,先執行命令行net start mongodb啓動它
from pymongo import MongoClient
 
class DoubanbookPipeline(object):
    def open_spider(self, spider):
        self.client = MongoClient('localhost', 27017)
        self.db = self.client.豆瓣讀書
        self.table = self.db.九分以上榜單
 
    def close_spider(self, spider):
        self.client.close()
 
    def process_item(self, item, spider):
        self.table.insert_one(dict(item))
        return item
*******************分割線*******************
五、settings.py:
 
import random
from fake_useragent import UserAgent
 
ROBOTSTXT_OBEY=False    #改
CONCURRENT_REQUESTS=64    #取注&改
REACTOR_THREADPOOL_MAXSIZE=20    #增
RETRY_TIMES=3    #增
DOWNLOAD_TIMEOUT=4    #增
DOWNLOAD_DELAY=random.random()/5    #取注&改
DEFAULT_REQUEST_HEADERS={'User-Agent':UserAgent().random}    #取注&改
ITEM_PIPELINES={……}    #取注,寫入表格、數據庫或下文件時要用到
 
FEED_FORMAT='csv'
FEED_URI='file://E:/douban.csv'
FEED_EXPORT_ENCODING='gbk'
 
#使用scrapy_redis緩存庫的6行配置:
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
SCHEDULER_PERSIST = True
REDIS_START_URLS_AS_SET=True
SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
****************************************分割線****************************************
Egの知乎Live的各條主題及其演講者:
 
一、scrapy startproject zhihuLive;cd zhihuLive;scrapy genspider zHLive api.zhihu.com
 
main.py:
import os
os.system('scrapy crawl zHLive')
*******************分割線*******************
二、items.py:
 
title=scrapy.Field()
speaker=scrapy.Field()
*******************分割線*******************
三、zHLive.py:
 
import scrapy,json
from ..items import ZhihuliveItem
item=ZhihuliveItem()
 
class ZhliveSpider(scrapy.Spider):  #CrawlSpider要用LinkExtractor,參數tags和attrs限制取json裏的url
    name='zHLive'
    allowed_domains=['api.zhihu.com']
    start_urls=['https://api.zhihu.com/lives/homefeed?limit=10&offset=10&includes=live']
 
    def parse(self, response):
        result=json.loads(response.text)
        if result['data']:  #後面內容爲空的網頁,依然有['paging']['next']等,但data爲[]
            for x in result['data']:
                item['title']=x['live']['subject']
                item['speaker']=x['live']['speaker']['member']['name']
                yield item
            nextPageUrl=result['paging']['next'] + '&includes=live'
            yield scrapy.Request(url=nextPageUrl,callback=self.parse)
*******************分割線*******************
4の前言——SQLite在PyCharm中的使用:
 
①配置SQLite:(MySql相似)
最右側的Database→加號→DataSource→選Sqlite(Xerial); sqlite在Pycharm中首次用時,先安裝驅動,即點擊左下角的Download連接。
②建立sqlite數據庫和表:
選一路徑如本項目所在的livespider,數據庫命名爲zhihulive.db ,點擊apply和ok→PyCharm主界面右側,依次點擊Database 、zhihulive.db 、main,加號、table,表命名爲LiveTable→依次點擊加號來增長3個字段:id爲INTEGER 主鍵 自增,title和speaker均爲TEXT型(除數據類型在點擊加號時已自動寫出外,主鍵啦、自增啦等其餘特性,只需敲1個字母如k、a,即自動彈出全稱)→點擊Execute。
 
補充の表操做如清空:右鍵表並選Open Console→DELETE FROM LiveTable→點左上角的綠三角。
***************分割線***************
四、pipelines.py:
 
import sqlite3
import MySQLdb
 
class ZhihulivePipeline(object):
    def open_spider(self,spider):
        #sqlite要寫絕對路徑,不然可能報錯sqlite3.OperationalError: no such table: LiveTable
        #self.conn = sqlite3.connect('E:/py/zhihulive.sqlite') #若無此sqlite庫,則自動建立個
 
        #鏈接mysql要寫4+2個參數;而sqlite只需完整路徑。與軟件Navicat鏈接它倆的界面一致
        self.conn=MySQLdb.connect(host='localhost',port=3306,user='chengy',password='',
            db='novel',charset='utf8mb4')  #現有utf8庫的鍵名:mysql是db,django是name
 
        self.cur=self.conn.cursor()
        #self.cur.execute('create table if not exists LiveTable(speaker varchar(19),title text)')
 
    def close_spider(self,spider):
        self.cur.close()
        self.conn.close()
 
    def process_item(self, item, spider):
        speaker = item['speaker']
        title = item['title']
        #格式化方式{},Sqlite和MySQL都支持;Sqlite還支持?,MySQL還支持%s
        # sql='insert into LiveTable(speaker,title) values(?,?)'  #Sqlite用?
        # sql='insert into LiveTable(speaker,title) values(%s,%s)'    #MySQL用%s
        # self.cur.executemany(sql,[(speaker,title),])   #.execute(sql,(speaker,title))
        sql=f'insert into LiveTable(speaker,title) values("{speaker}","{title}")'
        self.cur.execute(sql)
        self.conn.commit()
        return item
*******************分割線*******************
五、settings.py:
 
import random
from fake_useragent import UserAgent
 
ROBOTSTXT_OBEY=False    #改
CONCURRENT_REQUESTS=64    #取注&改
REACTOR_THREADPOOL_MAXSIZE=20    #增
RETRY_TIMES=3    #增
DOWNLOAD_TIMEOUT=4    #增
DOWNLOAD_DELAY=random.random()/5    #取注&改
DEFAULT_REQUEST_HEADERS={'User-Agent':UserAgent().random}    #取注&改,反爬頭Authorization已棄用
ITEM_PIPELINES={……}    #取注
#JOBDIR='jobZhihuLive'    #若選擇Terminal運行可啓用本句,Ctrl+C可斷點續爬
****************************************分割線****************************************
Egの整站取百科的詞條:
 
一、scrapy startproject baiduBaike;cd baiduBaike;scrapy genspider -t crawl baike baike.baidu.com
 
main.py: 
import os
os.system('scrapy crawl zhihu')
*******************分割線*******************
二、items.py:
 
詞條 = scrapy.Field()
網址 = scrapy.Field()
編輯 = scrapy.Field()
更新 = scrapy.Field()
建立者 = scrapy.Field()
*******************分割線*******************
三、baike.py:
 
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import BaidubaikeItem
item = BaidubaikeItem()
 
class BaikeSpider(CrawlSpider):   #scrapy.Spider:NotImplementedError
    name = 'baike'
    allowed_domains = ['baike.baidu.com']
    start_urls = ['https://baike.baidu.com']
    #用start_requests()請求個start_urls後,再用LinkExtractor的正則匹配它返回的response
    #Rule的首參LinkExtractor,allow等主要參數的值也但是();二參callback的值是str,指函數名
    rules = (Rule(LinkExtractor(('/item/',)), 'parseList', follow=True),)
 
    def parseList(self, response):
        try:    #有的詞條是個白板,沒有編輯次數、最近更新等內容
            item['詞條']=response.css('#query::attr(value)').extract_first()
            item['網址']=response.url
            item['編輯']=response.css('.description li:nth-child(2)::text').extract_first().split(':')[1]
            item['更新']=response.css('.j-modified-time::text').extract_first()
            item['建立者']=response.css('.description .show-userCard::text').extract_first()
            yield item
        except:
            pass
*******************分割線*******************
四、pipelines.py:
 
import sqlite3
 
class BaidubaikePipeline(object):
    def open_spider(self,spider):
        self.conn = sqlite3.connect(' E:/BaiduBaike.sqlite ')
        self.cur = self.conn.cursor()
        self.cur.execute('create table if not exists baike(詞條 varchar(19),\
          網址 varchar(90),編輯 varchar(7),更新 varchar(10),建立者 varchar(19))')
 
    def close_spider(self,spider):
        self.cur.close()
        self.conn.close()
 
    def process_item(self, item, spider):
        keys=','.join(item.keys())
        values=','.join(len(item) * '?')    #Sqlite用的?而非MySql那種%s
        sql=f'insert into baike({keys}) values({values})'   #key和value是一一對應的,序並沒亂
        self.cur.executemany(sql,[tuple(item.values()),])
        self.conn.commit()
        return item
*******************分割線*******************
五、settings.py:
 
import random
from fake_useragent import UserAgent
 
ROBOTSTXT_OBEY=False    #改
CONCURRENT_REQUESTS=64    #取注&改
REACTOR_THREADPOOL_MAXSIZE=20    #增
RETRY_TIMES=3    #增
DOWNLOAD_TIMEOUT=4    #增
DOWNLOAD_DELAY=random.random()/5    #取注&改
DEFAULT_REQUEST_HEADERS={'User-Agent':UserAgent().random}    #取注&改
ITEM_PIPELINES={……}    #取注
#JOBDIR='jobBaiduBaike'
****************************************分割線****************************************
Egの整站下mzitu的圖片:
 
一、scrapy startproject mzitu;cd mzitu;scrapy genspider-t crawl mv www.mzitu.com
 
main.py: 
import os
os.system('scrapy crawl mv')
*******************分割線*******************
二、items.py:
 
name = scrapy.Field()
referer = scrapy.Field()
imgUrls = scrapy.Field()
*******************分割線*******************
三、mv.py:
 
方法①:假如真實的圖片網址有規律,每一個人的全部圖址可直接推算出——
 
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider,Rule
from ..items import MzituItem  #yield item在Request後,item賦值不可寫在類函數外
 
import re
noName=r'[\\/:*?"<>|]'
 
pattern=re.compile('\d+(?=.jpg)') #預搜索;方法②無需pattern句
#延用查找欄第n個()內的值,PyCharm用…$n…,re.sub用r'…\n…'
#pattern=re.compile('\d+(.jpg)')  #若用此pattern,則下文替換處改成.sub(r'%0.2d\1'
 
class MvSpider(CrawlSpider):
    name = 'mv'
    allowed_domains = ['www.mzitu.com']
    start_urls = ['http://www.mzitu.com/']
    rules=(Rule(LinkExtractor(('mzitu.com/\d{1,6}$',)),'parseAlbum',follow=True),)
 
    def parseAlbum(self, response):
        item=MzituItem()    #用法②,若item句在函數外,A的部分圖去了E目錄,E的圖在W等
        #num=response.css('.pagenavi').xpath('a//text()').extract()[-2]
        #print('總頁數:',num)   #xpath中/爲子,//爲後代;當前節點下的子標籤,前無/
        num=response.css('.pagenavi ::text').extract()[-3]
        num=int(num)+1
        name=response.css('.main-title::text').extract_first()
        item['name']=re.sub(noName,' ',name).strip()    #英文標點換爲空格,首尾的不要
        item['referer']=response.url
        #上述代碼是法①和法②所通用的,下面開始出現分歧:
        #2017年的真實圖片網址有規律,就不請求各page來獲取了;前幾年無規律的圖址用法②
        realUrl=response.css('.main-image img::attr(src)').extract_first()
        if 'net/2017/' in realUrl:
            item['imgUrls']=[pattern.sub('%0.2d' %page,realUrl) for page in range(1,num)]
            yield item
****方法①&②的分割線****
方法②:假如真實的圖片網址無規律,每人的全部圖址,只能逐一請求她的各page頁後才能取出:
 
        #本例定位出的圖址在[ ]內沒提取,爲兼容pipelines的get_media_requests()的循環寫法
        #不少網站如本例17年的圖片網址有規律,可直接全推算出放在[ ]內,沒必要請求各page頁來提取
        item['imgUrls'] = response.css('.main-image img::attr(src)').extract()
        yield item  #此response.url同第1頁,循環始於1會報錯no more duplicates,故第1頁獨寫
        #或在settings中加一行HTTPERROR_ALLOWED_CODES=[301,404]?有空了驗證下
        for page in range(2, num):
            pageUrl=response.url+'/'+str(page)
            yield scrapy.Request(pageUrl,self.getRealUrl,meta={'item':item})
 
    def getRealUrl(self, response):
        item=response.meta['item']
        item['imgUrls']=response.css('.main-image img::attr(src)').extract()
        yield item  #直接寫在yield Request下面會存放混亂,故每解析出1張圖片網址後就去下載
*******************分割線*******************
四、pipelines.py:
 
from scrapy import Request
from scrapy.pipelines.images import ImagesPipeline
from fake_useragent import UserAgent
 
#對於自動生成的類,父類改用ImagesPipeline,小改下父類的倆方法
class MzituPipeline(ImagesPipeline):
    def get_media_requests(self, item, info):
        #①cannot create weak reference to 'list' object——
        #yield和列表解析不能共用:或仿父類的源碼用return+列表解析,或用for循環+yield
        #②Request中添個meta參數path,供下文的file_path()的request調用;path最好當場寫全
        h={'User-Agent':UserAgent().random,'referer':item['referer']}
        return [Request(x,headers=h,meta={'path':item['name']+'/'+x.split('/')[-1]}) \
                for x in item['imgUrls']]
 
    def file_path(self, request, response=None, info=None):
        #除非上文的Request中加了callback=self.file_path,這裏或許能用response.meta['path']
        return request.meta['path']
*******************分割線*******************
五、settings.py:
 
import random
from fake_useragent import UserAgent
ROBOTSTXT_OBEY=False    #改
CONCURRENT_REQUESTS=64    #取注&改
REACTOR_THREADPOOL_MAXSIZE=20    #增
RETRY_TIMES=3    #增
DOWNLOAD_TIMEOUT=4    #增
DOWNLOAD_DELAY=random.random()*3    #取注&改,下圖比下數據慢故延時久點
DEFAULT_REQUEST_HEADERS={'User-Agent':UserAgent().random}    #取注&改
ITEM_PIPELINES={……}    #取注

#JOBDIR = 'jobMzitu'
IMAGES_STORE = 'F:/mzitu/'
IMAGES_MIN_WIDTH = 400; IMAGES_MIN_HEIGHT = 400
IMAGES_EXPIRES = 30 #30天內爬過的再也不爬?
****************************************分割線****************************************
Egの登陸知乎,並爬各用戶的主頁、回答數、被關注等信息,存至excel:
 
一、scrapy startproject zhihuLogin;
cd zhihuLogin;scrapy genspider zhihuUsers www.zhihu.com
 
main.py: 
import os
os.system('scrapy crawl zhihuUsers')
*******************分割線*******************
二、items.py:
 
    name = scrapy.Field()
    gender = scrapy.Field()
    answer_count = scrapy.Field()
    articles_count = scrapy.Field()
    follower_count = scrapy.Field()
    following_count = scrapy.Field()
    url_token = scrapy.Field()
*******************分割線*******************
三、zhihuUsers.py:
 
phone='13889083841'
password='登陸密碼'
 
topUsers=set()
domain='https://www.zhihu.com/'
firstUser='liaoxuefeng'
firstUrl = domain+'api/v4/members/{0}/followees?include=data[*].\
    answer_count,articles_count,follower_count,following_count'
 
from scrapy import Spider,Request,FormRequest
from ..items import ZhihuloginItem
from io import BytesIO
from PIL import Image
import json,time,urllib
 
class ZhihuusersSpider(Spider):
    name = 'zhihuUsers'
    allowed_domains = ['www.zhihu.com']
    # start_urls = ['https://www.zhihu.com/']
 
    def start_requests(self):   #settings中的COOKIES_ENABLED = False,保持默認的註銷狀態
        #return [Request(domain,self.captcha,dont_filter=True)]
        yield Request(domain,self.captcha,dont_filter=True)
 
    def captcha(self,response):
        xsrf=response.css('[name=_xsrf]::attr(value)').extract_first()
        r=int(time.time() * 1000)
        captchaUrl = domain+f'captcha.gif?r={r}&type=login&lang=cn'
        yield Request(captchaUrl,self.getCaptcha,meta={'xsrf':xsrf})    #Request請求的寫法①
 
        #寫法②:有時不得不把1個Request生成器分3句寫,如拉勾網給早期請求所取到的cookies
        #再添個鍵:request.cookies['LGUID']=……['user_trace_token'][0]
        #request=Request(captchaUrl,self.getCaptcha);request.meta['xsrf']=xsrf;yield request
 
    def getCaptcha(self,response):
        #cookies=response.request.headers.get(b'Cookie')
        #if cookies: #Cookie的值依然是b節碼,先轉爲str並去除空格,最後用parse_qs()轉爲{}
        #    xsrf=urllib.parse.parse_qs(cookies.decode().replace(' ', ''))['_xsrf'][0]
        #print('cookies中的某參:',response.meta['xsrf'],xsrf,type(xsrf),sep='\n')
 
        Image.open(BytesIO(response.body)).show()
        captcha=tuple(int(x)*23 for x in input('輸入各倒字的序號如1-3,自1始,以-隔:').split('-'))
        if len(captcha)==2:  # 目前的驗證碼,大部分時候是兩個倒立漢字,偶爾是一個
            captcha='{"img_size":[200,44],"input_points":[[%s,23],[%s,23]]}' % captcha
        elif len(captcha)==1:
            captcha='{"img_size":[200,44],"input_points":[[%s,23]]}' % captcha
        fd={'captcha_type': 'cn','captcha': captcha,'_xsrf': response.meta['xsrf'],\
            'phone_num': phone, 'password': password}
        yield FormRequest(domain+'login/phone_num',self.login,formdata=fd)
 
    def login(self,response):
        loginResult=json.loads(response.text)
        if loginResult['r']==0:
            print('登陸成功,開始抓取用戶信息。。。')
            yield Request(firstUrl.format(firstUser),self.followers)
        else:
            print('登陸失敗。。。',loginResult,sep='\n')
 
    def followers(self,response):
        item=ZhihuloginItem()
        result=json.loads(response.text)
        if result['data']:
            for user in result['data']:
                item['name']=user['name']
                item['gender']='男' if user['gender'] else '女'
                item['answer_count']=user['answer_count']
                item['articles_count']=user['articles_count']
                item['follower_count']=user['follower_count']
                item['following_count']=user['following_count']
                item['url_token']='https://www.zhihu.com/people/'+user['url_token']
                if user['follower_count']>10000: topUsers.add(user['url_token'])
                yield item
        if result['paging']['is_end']==False:   #網頁源碼中的false沒加引號,是布爾值
            nextPageUrl=result['paging']['next']
            yield Request(nextPageUrl,self.followers)
        else:
            nextTopUser=topUsers.pop()
            yield Request(firstUrl.format(nextTopUser),self.followers)
*******************分割線*******************
四、pipelines.py:
 
import time
from openpyxl import Workbook
 
class ZhihuloginPipeline(object):
    wb = Workbook()
    ws = wb.active
    ws.append(['姓名','性別','回答','文章','關注他','他關注','主頁'])
 
    def process_item(self, item, spider):
        row = [item['name'],item['gender'],item['answer_count'],item['articles_count'],\
               item['follower_count'],item['following_count'],item['url_token']]
        self.ws.append(row)
        path = time.strftime('zhihuUsers %x.xlsx', time.localtime()).replace('/', '-')
        self.wb.save(f'E:/{path}')  #time.strftime()的首參字串不能有中文
        return item
*******************分割線*******************
五、settings.py:
 
import random
from fake_useragent import UserAgent
 
ROBOTSTXT_OBEY=False    #改
CONCURRENT_REQUESTS=64    #取注&改
REACTOR_THREADPOOL_MAXSIZE=20    #增
RETRY_TIMES=3    #增
DOWNLOAD_TIMEOUT=4    #增
DOWNLOAD_DELAY=random.random()/5    #取注&改
DEFAULT_REQUEST_HEADERS={'User-Agent':UserAgent().random}    #取注&改,反爬頭Authorization已棄用
ITEM_PIPELINES={……}    #取注
#JOBDIR='jobZhihuLogin'
相關文章
相關標籤/搜索