【Python爬蟲】使用遞歸函數(shù)遇到的問題

一讹俊、遞歸函數(shù)的基本使用

遞歸函數(shù)是編程中常用的技術(shù)之一,它允許函數(shù)調(diào)用自身來解決問題渐裸。遞歸函數(shù)的使用通常涉及以下幾個基本要素:

1. 基案(Base Case)

基案是遞歸終止的條件巫湘。沒有基案的遞歸函數(shù)將無限調(diào)用自身,最終導(dǎo)致棧溢出錯誤昏鹃。

2. 遞歸案(Recursive Case)

遞歸案是函數(shù)調(diào)用自身的情況尚氛,它應(yīng)該逐漸向基案靠攏。

3. 調(diào)用自身

遞歸函數(shù)必須在某個點(diǎn)調(diào)用自身洞渤,并且每次調(diào)用都應(yīng)該使問題更接近基案怠褐。

示例:計(jì)算階乘

階乘是遞歸的經(jīng)典例子,因?yàn)樗x為n! = n * (n-1)!您宪,其中0!定義為1奈懒。

def factorial(n):
    # 基案:0的階乘是1
    if n == 0:
        return 1
    # 遞歸案:n的階乘是n乘以(n-1)的階乘
    else:
        return n * factorial(n - 1)

# 測試遞歸函數(shù)
print(factorial(5))  # 輸出: 120

示例:斐波那契數(shù)列

斐波那契數(shù)列是另一個遞歸的例子,其中每個數(shù)字是前兩個數(shù)字的和宪巨。

def fibonacci(n):
    # 基案:第0個和第1個斐波那契數(shù)是1
    if n == 0 or n == 1:
        return 1
    # 遞歸案:第n個斐波那契數(shù)是前兩個斐波那契數(shù)的和
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

# 測試遞歸函數(shù)
print(fibonacci(6))  # 輸出: 8

注意事項(xiàng)

  • 確保遞歸有明確的基案磷杏,以避免無限遞歸。
  • 考慮遞歸的深度捏卓,過深的遞歸可能導(dǎo)致棧溢出极祸。
  • 分析遞歸的效率慈格,有些問題可能不適合用遞歸解決,或者遞歸解決方案可能不是最高效的遥金。
  • 尾遞歸優(yōu)化:某些編程語言支持尾遞歸優(yōu)化浴捆,Python的CPython解釋器默認(rèn)不支持,但可以通過手動轉(zhuǎn)換為循環(huán)來避免棧溢出稿械。

遞歸是一種強(qiáng)大的工具选泻,但需要謹(jǐn)慎使用,以確保它不會引發(fā)性能問題或棧溢出美莫。

二页眯、遞歸函數(shù)遇到的問題:

  1. page_data=[]定義在遞歸函數(shù)內(nèi),導(dǎo)致每次遞歸調(diào)用會把page_data的值重置為[]厢呵。
  2. 遞歸調(diào)用的結(jié)果被添加到 page_data 列表中窝撵,而不是直接賦值給 page_data。這導(dǎo)致即使在找不到下一頁的情況下襟铭,函數(shù)也不會立即返回 page_data碌奉,而是繼續(xù)被遞歸調(diào)用,導(dǎo)致無限遞歸寒砖。
  3. 缺少基案:原代碼只有存在下一頁時的遞歸案:獲取當(dāng)前頁的全部文章并追加到page_data道批,但缺少基案:不存在下一頁時,直接返回page_data入撒。導(dǎo)致多次調(diào)試打印page_data的值始終為[]隆豹。增加基案后,page_data的值返回正確茅逮。
  4. 將遞歸函數(shù)改為循環(huán)璃赡,以避免潛在的遞歸深度問題。
  5. 性能問題:遞歸可能不如迭代高效献雅,尤其是在每次遞歸調(diào)用都有較大開銷的情況下碉考。分析遞歸的性能,考慮是否有更優(yōu)的迭代解決方案挺身。
def parse_page_soup(current_url, headers, context, page_data=None):
    # 錯誤用法:每次遞歸調(diào)用page_data的值都被重置為[]侯谁。
    page_data = []
    
    # 處理當(dāng)前頁的數(shù)據(jù)
    soup = get_soup(current_url, headers, context)
    if not soup:
        return page_data
    
    # 處理列表跳轉(zhuǎn)至詳情頁的數(shù)據(jù)
    tag_list = soup.select('div.AllListCon a')    
    for tag in tag_list:
        article_url = tag.get('href')
        article_soup = get_soup(article_url, headers, context)
        if article_soup:
            page_data.append(parse_article_soup(article_soup))

    # 查找下一頁
    next_page = soup.find('a', class_='next')
    if next_page:
        next_page_url = next_page.get('href')
        # 錯誤用法,遞歸調(diào)用+合并結(jié)果
        page_data += parse_page_soup(next_page_url, headers, context, page_data)

方案一:修改遞歸函數(shù)的調(diào)用方法

def parse_page_data(current_url, headers, context, page_data=None):
    # 正確用法:增加對page_data值的判斷章钾。
    if page_data is None:
        page_data = []
        
    # 處理當(dāng)前頁的數(shù)據(jù)
    soup = get_soup(current_url, headers, context)
    if not soup:
        return page_data
    
    # 處理列表跳轉(zhuǎn)至詳情頁的數(shù)據(jù)
    tag_list = soup.select('div.AllListCon a')
    
    for tag in tag_list:
        article_url = tag.get('href')
        article_soup = get_soup(article_url, headers, context)
        if article_soup:
            page_data.append(parse_article_soup(article_soup))
    # 查找下一頁
    next_page = soup.find('a', class_='next')
    if next_page:
        current_url = next_page.get('href')
        # 遞歸調(diào)用并立即返回結(jié)果
        return parse_page_data(current_url, headers, context, page_data)
    # 一定要加不滿足條件時的返回值墙贱,否則遞歸不滿足時由于沒有返回值會返回空
    else:
        return page_data

方案二:使用迭代(while循環(huán))替換遞歸函數(shù)

def parse_page_data(base_url, headers, context):
    page_data = []
    curr_page_url = base_url
    while curr_page_url:
        # 獲取當(dāng)前頁的soup對象
        soup = get_soup(curr_page_url, headers, context)
        if not soup:
            break  # 如果soup為空,直接跳出循環(huán)
        
        # 處理當(dāng)前頁的數(shù)據(jù)
        tag_list = soup.select('div.AllListCon a')
        for tag in tag_list:
            article_url = tag.get('href')
            if not article_url.startswith(('http:', 'https:')):
                article_url = urljoin(next_page_url, article_url)
            article_soup = get_soup(article_url, headers, context)
            if article_soup:
                page_data.append(parse_article_soup(article_soup))

        # 查找下一頁的鏈接
        next_page = soup.find('a', class_='next')
        if next_page:
            next_page_url = next_page.get('href')
            if not next_page_url.startswith(('http:', 'https:')):
                next_page_url = urljoin(next_page_url, next_page_url)
            curr_page_url = next_page_url
        else:
            break  # 如果沒有下一頁贱傀,跳出循環(huán)
    return page_data  # 循環(huán)結(jié)束后返回data_list

三惨撇、使用遞歸函數(shù)經(jīng)常遇到的問題詳細(xì)解析

使用遞歸函數(shù)時,開發(fā)者可能會遇到幾個常見問題府寒。以下是這些問題的詳細(xì)解析和解決方法:

1. 無限遞歸(Infinite Recursion)

如果遞歸函數(shù)沒有正確的基案或遞歸條件設(shè)置不當(dāng)魁衙,函數(shù)可能會無限調(diào)用自身报腔,導(dǎo)致程序永遠(yuǎn)不會停止。

解決方法

  • 確保遞歸函數(shù)有明確的基案剖淀,且遞歸調(diào)用逐步逼近基案纯蛾。

2. 棧溢出(Stack Overflow)

每次函數(shù)調(diào)用都會在內(nèi)存的調(diào)用棧上添加一條記錄。遞歸調(diào)用太深時纵隔,可能會超出調(diào)用棧的最大容量限制翻诉,導(dǎo)致棧溢出錯誤。

解決方法

  • 優(yōu)化遞歸邏輯巨朦,減少遞歸深度。
  • 使用尾遞歸優(yōu)化(如果語言支持)剑令。
  • 考慮使用迭代(循環(huán))代替遞歸糊啡。

3. 重復(fù)計(jì)算(Redundant Computations)

某些遞歸算法可能會多次計(jì)算相同的子問題,這在動態(tài)規(guī)劃問題中尤為常見吁津。

解決方法

  • 使用記憶化(Memoization)技術(shù)棚蓄,存儲已經(jīng)計(jì)算過的結(jié)果,避免重復(fù)計(jì)算碍脏。
  • 將遞歸轉(zhuǎn)換為動態(tài)規(guī)劃算法梭依。

4. 性能問題(Performance Issues)

遞歸可能不如迭代高效,尤其是在每次遞歸調(diào)用都有較大開銷的情況下典尾。

解決方法

  • 分析遞歸的性能役拴,考慮是否有更優(yōu)的迭代解決方案。
  • 使用尾遞歸優(yōu)化減少開銷钾埂。

5. 難以理解的邏輯(Complex Logic)

遞歸邏輯可能難以理解和維護(hù)河闰,尤其是對于初學(xué)者。

解決方法

  • 使用清晰的基案和遞歸邏輯褥紫。
  • 添加注釋和文檔姜性,說明遞歸的工作原理。

6. 語言或環(huán)境限制(Language or Environment Limitations)

不同的編程語言對遞歸的支持程度不同髓考,例如Python的默認(rèn)遞歸深度限制較低部念。

解決方法

  • 了解并設(shè)置語言的遞歸限制(如Python中的sys.setrecursionlimit())。
  • 使用迭代或增加棸惫剑空間儡炼。

7. 尾遞歸未優(yōu)化(Tail Recursion Not Optimized)

尾遞歸是一種特殊的遞歸形式,其中遞歸調(diào)用是函數(shù)體中的最后一個操作查蓉。盡管尾遞歸理論上可以優(yōu)化以減少棧使用射赛,但并非所有語言或解釋器/編譯器都會自動進(jìn)行這種優(yōu)化。

解決方法

  • 手動轉(zhuǎn)換尾遞歸為循環(huán)奶是。
  • 使用支持尾遞歸優(yōu)化的語言或工具楣责。

8. 資源消耗(Resource Consumption)

遞歸函數(shù)可能會消耗大量內(nèi)存或其他資源竣灌,尤其是在遞歸樹非常深的情況下。

解決方法

  • 優(yōu)化算法以減少資源消耗秆麸。
  • 使用生成器等技術(shù)減少內(nèi)存占用初嘹。

9. 調(diào)試?yán)щy(Debugging Difficulty)

遞歸函數(shù)可能難以調(diào)試,因?yàn)檎{(diào)用椌谌ぃ可能非常深屯烦,且錯誤可能在遞歸的深層發(fā)生。

解決方法

  • 使用調(diào)試工具逐步跟蹤遞歸調(diào)用房铭。
  • 添加打印語句以跟蹤遞歸的進(jìn)度和狀態(tài)驻龟。

通過了解和解決這些問題,你可以更有效地使用遞歸函數(shù)缸匪,并避免常見的陷阱翁狐。在某些情況下,迭代可能是更好的選擇凌蔬,特別是當(dāng)遞歸邏輯復(fù)雜或存在性能問題時露懒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砂心,隨后出現(xiàn)的幾起案子懈词,更是在濱河造成了極大的恐慌,老刑警劉巖辩诞,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坎弯,死亡現(xiàn)場離奇詭異,居然都是意外死亡译暂,警方通過查閱死者的電腦和手機(jī)荞怒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秧秉,“玉大人褐桌,你說我怎么就攤上這事∠笥” “怎么了荧嵌?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長砾淌。 經(jīng)常有香客問我啦撮,道長,這世上最難降的妖魔是什么汪厨? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任赃春,我火速辦了婚禮,結(jié)果婚禮上劫乱,老公的妹妹穿的比我還像新娘织中。我一直安慰自己锥涕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布狭吼。 她就那樣靜靜地躺著层坠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刁笙。 梳的紋絲不亂的頭發(fā)上破花,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機(jī)與錄音疲吸,去河邊找鬼座每。 笑死,一個胖子當(dāng)著我的面吹牛摘悴,可吹牛的內(nèi)容都是我干的峭梳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼烦租,長吁一口氣:“原來是場噩夢啊……” “哼延赌!你這毒婦竟也來了除盏?” 一聲冷哼從身側(cè)響起叉橱,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎者蠕,沒想到半個月后窃祝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡踱侣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年粪小,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抡句。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡探膊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出待榔,到底是詐尸還是另有隱情逞壁,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布锐锣,位于F島的核電站腌闯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏雕憔。R本人自食惡果不足惜姿骏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斤彼。 院中可真熱鬧分瘦,春花似錦蘸泻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至趁冈,卻和暖如春歼争,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渗勘。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工沐绒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人旺坠。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓乔遮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親取刃。 傳聞我的和親對象是個殘疾皇子蹋肮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容