编写可靠的多线程蜘蛛程序

编写可靠的多线程蜘蛛程序

技术

〔此篇专为QQ群[17371752]“搜索引擎.数据.蜘蛛”中的朋友做主题研讨之用〕

1. 蜘蛛程序长啥样?

蜘蛛程序的是搜索引擎中最关键的后台程序之一,它必须十分可靠,可以长期运行而无需经常维护。但是我往往看到许多朋友开始做蜘蛛程序的时候总是将很多精力放到了界面上。个人觉得这是没有必要的,我们应该注重这个程序的可靠性和效率。
(图1. Coolgobot,笑侃的第一个蜘蛛程序)

(图2. 有简单界面的蜘蛛程序)

(图3. Webus系统中最有效的蜘蛛程序之一)

2. 蜘蛛程序的结构 (图4. 蜘蛛程序结构)

2.1 用队列来传递Url数据
在图4中,Url Reader将Url数据读取到一个队列中,再由下载线程去队列中取Url并且下载。使用队列的好处是可以对于流程容易控制,而且方便实现多线程方式。

2.2 两种多线程方案
对于蜘蛛程序而言有两种多线程方案
a. 抢先式多线程
这种方法简单来说就是一开始只有一个线程在工作,当这个线程下载了一个页面之后,Parser马上从页面中提取新的Url List,然后针对每个Url,程序都会启动一个新的线程去下载,直到达到最大线程数为止。
b. 循环式多线程
这种方法就是一开始就开启N个线程,每个线程都会自己去Queue中取Url进行下载工作,完成一个,就取下一个。直到没有新的Url了,线程就进入休眠(Sleep)状态。
这两种方案中,我比较倾向于b方案,因为采用这种方案将使得程序的结构更加清晰,功能模块划分也更加合理。所以后面我所说的多线程都是指循环式的多线程方案。

2.3 将临时页面数据保存为文件
我看到很多朋友开始写蜘蛛程序时,为了追求高效率,将下载的页面数据都放到一个Queue中,等待分析线程取逐一分析。这种方式固然可以让整个程序跑起 来,但是能够跑多长时间就难说了。因为此时整个系统的稳定性和Queue的大小成反比。Queue越大,占用内存就越多,系统稳定性就越差。可能运行一两 天看起来还不错,要是时间一长,程序就变得不那么健康了,最后就会崩溃。
我们不妨将临时页面保存为文件,然后用一个Queue将要处理得文件名传递给分析线程,再由分析线程去处理对应的文件。这种处理方式就比上面的一种好得多,系统的可靠性又大大加强了。

2.4 分析线程 or 分析程序?
页面下载下来,需要分析哪些数据和具体的需求相关,我们这里就不讨论这个问题了。这里说说“分析线程”还是“分析程序”的问题。
其实将分析部分作为线程还是单独的程序来实现在逻辑上是没有什么区别的。这两种方法应该可是实现同样的可靠性和效率。但是作为程序会比作为线程实现起来麻烦一些。
作为线程,下载线程和分析线程之间的数据传递通过一个Queue就行了。如果作为程序,就需要考虑到两个进程之间的通讯,这样会比较麻烦一些。

2.5 Url消重
为了避免蜘蛛程序重复下载同一个页面,我们需要对Url消重。这里有两种方案可以实现Url消重。一种利用数据库,针对Url字段建立唯一约束……。这是 一种偷懒的方法,当数据量不大时还是很有效的,主要是无需编程,实现简单。另外一种就是利用信息指纹的方法来实现。关于这部分,可以参看: http://googlechinablog.com/2006/08/blog-post.html

3. 我们要思考的问题

3.1 头号问题:OutOfMemoryException
如果没有搞错的话,内存溢出是蜘蛛程序会遇到的头号问题。它的特点是:不定性、突发性、灾难性。要解决这个问题,我们首先要改变自己的编程习惯。如果想要 自己的蜘蛛长命百岁,就要采取节制的内存策略,呵呵,内存就像脂肪,太多了终究是会导致疾病的。那为什么这个问题会体现出不定性、突发性呢?这和 WIN32的内存机制有关,关于这部分,可以参看: http://my.opera.com/talkinsmile/blog/show.dml/407303

3.2 异常管理
微软的大师说过:如果没有好的异常管理策略,就干脆不要尝试去管理。但他并不是要我们对异常采取听之任之的态度。至少我们应该捕获可能发生的异常。如果你希望自己的蜘蛛任劳任怨,不会罢工,就好好检查一下和以下名词相关的程序代码中是否已经做了完备的异常处理:
  • 网络
  • 数据库
  • 线程
  • 磁盘IO
  • 超出值范围
  • 数组下标溢出
  • 还有什么?太多了,大家自己去发现吧^_^


3.3 分布式
对于开发蜘蛛程序的人而言,首要关心的是“多线程”然后就是“分布式”了。分布式是一个比较复杂的问题,需要分析具体的部署条件和业务需求,从而选择一种合适的设计模式来实现分布式应用。
针对蜘蛛程序,我个人最喜欢的是管道式的分布式设计模式。所谓管道就是一个:Url -> 下载 -> 分析 -> 提交数据的完整过程,管道式分布式设计模式的思想就是通过一个管理器将很多这种管道都并起来一同工作,如果哪条管道出现问题了,也不会影响其它的管道正常 工作。
关于这部分,大家可以自己到找找,很多的。

讲完了,大家多多提出自己的方案或者想法,共同提高蜘蛛程序开发水平!
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 蜘蛛纸牌游戏是一款基于纸牌的单人游戏,玩家需要将纸牌按照花色和数字的顺序排列好,以完成游戏。以下是一个简单的蜘蛛纸牌游戏的程序实现: ```python import random # 定义纸牌花色和数字 suits = ['♠', '♥', '♣', '♦'] ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] # 定义牌堆类 class Deck: def __init__(self): self.cards = [] for suit in suits: for rank in ranks: self.cards.append(suit + rank) random.shuffle(self.cards) def draw_card(self): return self.cards.pop() # 定义游戏类 class SpiderSolitaire: def __init__(self): self.deck = Deck() self.tableau = [[], [], [], [], [], [], []] self.foundation = [[], [], [], []] for i in range(7): for j in range(i + 1): card = self.deck.draw_card() if j == i: self.tableau[j].append(card) else: self.tableau[j].append(' ') def display(self): print("Tableau:") for i in range(len(self.tableau)): print(f"{i+1}: ", end="") for j in range(len(self.tableau[i])): print(self.tableau[i][j], end=" ") print() print("Foundation:") for i in range(len(self.foundation)): print(f"{suits[i]}: ", end="") for j in range(len(self.foundation[i])): print(self.foundation[i][j], end=" ") print() def is_valid_move(self, source, dest): if source == dest: return False if len(self.tableau[source]) == 0: return False if len(self.tableau[dest]) == 0: return True source_card = self.tableau[source][-1] dest_card = self.tableau[dest][-1] source_rank = ranks.index(source_card[1:]) dest_rank = ranks.index(dest_card[1:]) if source_rank == dest_rank + 1 and source_card[0] != dest_card[0]: return True else: return False def move_card(self, source, dest): if self.is_valid_move(source, dest): card = self.tableau[source].pop() self.tableau[dest].append(card) def is_game_over(self): for i in range(len(self.foundation)): if len(self.foundation[i]) != 13: return False return True def play(self): while not self.is_game_over(): self.display() source = int(input("Select source tableau (1-7): ")) - 1 dest = int(input("Select destination tableau or foundation (1-4): ")) - 1 if dest < 0 or dest > 3: continue if self.is_valid_move(source, dest): self.move_card(source, dest) else: print("Invalid move!") print("You win!") # 开始游戏 game = SpiderSolitaire() game.play() ``` 该程序实现了一个基本的蜘蛛纸牌游戏,玩家可以通过输入源牌堆和目标牌堆来进行牌的移动,直到将所有纸牌按照花色和数字的顺序排列好。 ### 回答2: 蜘蛛纸牌是一种单人纸牌游戏,目标是将52张扑克牌按照特定规则整理成8组完整的纸牌序列。编写一个蜘蛛纸牌游戏的程序需要以下步骤: 1. 初始化游戏:创建一个包含52张扑克牌的初始牌组。可以使用数字和花色来表示每个扑克牌,如红桃12代表红桃Q。 2. 发牌:将初始牌组中的牌分发到游戏区域中的8个列中,每列一张牌,其中4个列中的牌背面朝下。 3. 移动规则:根据游戏规则,玩家可以移动牌组来整理纸牌序列。可以将一个或多个由低到高排列的纸牌序列移动到另一个牌组的末端,前提是这些纸牌序列是同花色且按照降序排列的。此外,可以将K(13)以及一张或多张低于K的纸牌移动到空列。 4. 判定胜利条件:在游戏进行过程中,判定是否已经达到胜利条件,即8个列中的每列都是由K到A的降序排列。 5. 用户交互:编写程序中,需要设置与用户的交互界面,包括菜单选项、指令输入和游戏状态的显示等。 6. 实时保存和读取:为了保证游戏的连续性,需要实时保存和读取游戏进度,以便玩家在中途退出游戏后可以继续进行。 7. 游戏结束:当达到胜利条件或者无法进行任何移动时,游戏结束,显示游戏结果。 编写蜘蛛纸牌游戏的程序需要熟悉编程语言和算法,并合理设计游戏逻辑和界面交互。通过循序渐进的开发方式,可以逐步完善蜘蛛纸牌游戏程序,从而实现一个有趣的单人纸牌游戏。 ### 回答3: 蜘蛛纸牌游戏是一款单人玩的扑克牌游戏,它使用两副扑克牌(共104张牌)。游戏目标是将所有牌按照花色从K到A排列在纸牌桌上的8个基础框中,以完成整副牌的排序。 为了编写一个蜘蛛纸牌游戏的程序,我们可以按照以下步骤进行。 1. 创建一副有104张牌的扑克牌,并随机洗牌。 2. 创建一个牌桌,包含8个基础框和可用于移动牌的列。 3. 将洗好的牌分为10列,前4列每列有6张牌,后6列每列有5张牌。只有每一列的最底下一张牌是暴露的,其他牌是盖着的。 4. 创建游戏逻辑,允许玩家通过点击选中一张牌并将其移动到其他可用的位置。移动规则包括: - 单张牌可以移动到基础框中,如果目标基础框的顶部牌比被移动的牌小一个点数且花色相同。 - 一组以K开头(KQJ10···)的连续牌可以移到另一个列。 - 任何牌都可以移到空的列。 5. 提供一个计分系统,记录每一次移动和完成整副牌排序所用的时间。可以在程序中的界面上显示玩家的得分和时间。 6. 添加胜利条件,当所有的牌都按顺序排在基础框中时,游戏结束,显示玩家的得分和时间。 通过以上步骤,我们可以编写一个简单的蜘蛛纸牌游戏程序程序运行后,玩家可以通过点击来交互,并享受到类似实体游戏的乐趣。程序还可以记录多个玩家的成绩,促进玩家之间的竞争和比较。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值