【发布时间】:2010-09-11 06:50:26
【问题描述】:
我曾想过尝试编写一个简单的爬虫,它可能会爬取并为我们 NPO 的网站和内容生成其发现的列表。
有人对如何做到这一点有任何想法吗?您将爬虫指向哪里开始?它如何发回其发现并继续爬行?它怎么知道它发现了什么等等。
【问题讨论】:
标签: web-crawler
我曾想过尝试编写一个简单的爬虫,它可能会爬取并为我们 NPO 的网站和内容生成其发现的列表。
有人对如何做到这一点有任何想法吗?您将爬虫指向哪里开始?它如何发回其发现并继续爬行?它怎么知道它发现了什么等等。
【问题讨论】:
标签: web-crawler
可以肯定的是,您将重新发明轮子。但这里是基础:
将它们放在持久存储中,这样您就可以停止和启动爬虫而不会丢失状态。
算法是:
while(list of unvisited URLs is not empty) {
take URL from list
remove it from the unvisited list and add it to the visited list
fetch content
record whatever it is you want to about the content
if content is HTML {
parse out URLs from links
foreach URL {
if it matches your rules
and it's not already in either the visited or unvisited list
add it to the unvisited list
}
}
}
【讨论】:
while 循环中您可能想要做的第一件事是将URL 添加到already listed list... 否则如果有两个页面引用,您可能会陷入无限循环彼此...
爬虫的复杂部分是如果你想将它扩展到大量的网站/请求。 在这种情况下,您将不得不处理一些问题,例如:
无法将所有信息保存在一个数据库中。
没有足够的 RAM 来处理巨大的索引
多线程性能和并发性
爬虫陷阱(通过更改 url、日历、会话 ID... 创建无限循环)和重复内容。
从多台计算机上抓取
HTML 代码格式错误
来自服务器的持续 http 错误
没有压缩的数据库,这使您对空间的需求增加了大约 8 倍。
重新抓取例程和优先级。
使用压缩请求 (Deflate/gzip)(适用于任何类型的爬虫)。
还有一些重要的事情
尊重 robots.txt
每个请求都会有爬虫延迟,以免让 Web 服务器窒息。
【讨论】:
多线程网络爬虫
如果你想爬取大型网站,那么你应该编写一个多线程爬虫。 在文件/数据库中连接、获取和写入爬取的信息 - 这是爬取的三个步骤,但如果您使用单线程,那么您的 CPU 和网络利用率将会倾泻而出。
一个多线程网络爬虫需要两个数据结构——linksVisited(这应该实现为一个 hashmap 或 trai)和 linksToBeVisited(这是一个队列)。
网络爬虫使用 BFS 遍历万维网。
基本网络爬虫的算法:-
重复步骤 2 到 5,直到队列为 linksToBeVisited 为空。
这是一个关于如何同步线程的代码 sn-p....
public void add(String site) {
synchronized (this) {
if (!linksVisited.contains(site)) {
linksToBeVisited.add(site);
}
}
}
public String next() {
if (linksToBeVisited.size() == 0) {
return null;
}
synchronized (this) {
// Need to check again if size has changed
if (linksToBeVisited.size() > 0) {
String s = linksToBeVisited.get(0);
linksToBeVisited.remove(0);
linksVisited.add(s);
return s;
}
return null;
}
}
【讨论】:
爬虫的概念很简单。
您通过 HTTP GET 获得一个根页面,对其进行解析以查找 URL 并将它们放入队列中,除非它们已经被解析(因此您需要一个已解析页面的全局记录)。
您可以使用 Content-type 标头找出内容的类型,并将您的爬虫限制为仅解析 HTML 类型。
您可以去除 HTML 标签以获取纯文本,您可以对其进行文本分析(以获取标签等,页面的主要内容)。如果你有这样的高级,你甚至可以在图像的 alt/title 标签上这样做。
在后台你可以有一个线程池从队列中吃 URL 并做同样的事情。你当然想限制线程数。
【讨论】:
如果您的 NPO 的网站相对较大或比较复杂(具有动态页面,可以有效地创建一个“黑洞”,例如带有“第二天”链接的日历),您最好使用真正的网络爬虫,例如 @ 987654321@
如果网站总共有几页,则只需使用 curl 或 wget 或您自己的即可。请记住,如果它们开始变大,或者您开始使脚本变得更复杂,只需要使用真正的爬虫,或者至少查看其源代码以了解它们在做什么以及为什么这样做。
一些问题(还有更多):
【讨论】:
维基百科有一篇关于web crawlers 的好文章,涵盖了许多算法和注意事项。
但是,我不会费心编写自己的爬虫。工作量很大,因为你只需要一个“简单的爬虫”,我想你真正需要的只是一个off-the-shelf crawler。有很多免费和开源的爬虫可以满足您的所有需求,而您只需做很少的工作。
【讨论】:
您可以制作一个单词列表,并为在 google 搜索的每个单词创建一个线程。
然后每个线程将为它在页面中找到的每个链接创建一个新线程。
每个线程应该写下它的内容在数据库中查找。当每个线程完成读取页面时,它就会终止。
您的数据库中有一个非常大的链接数据库。
【讨论】:
使用 wget,做一个递归的网络吸吮,它将所有文件转储到你的硬盘上,然后编写另一个脚本来检查所有下载的文件并分析它们。
编辑:或者可能是curl而不是wget,但是我对curl不熟悉,不知道它是否会像wget那样递归下载。
【讨论】:
我正在使用开放搜索服务器进行公司内部搜索,试试这个:http://open-search-server.com 它也是开放源。
【讨论】:
我在 .net 中使用响应式扩展做了一个简单的网络爬虫。
https://github.com/Misterhex/WebCrawler
public class Crawler
{
class ReceivingCrawledUri : ObservableBase<Uri>
{
public int _numberOfLinksLeft = 0;
private ReplaySubject<Uri> _subject = new ReplaySubject<Uri>();
private Uri _rootUri;
private IEnumerable<IUriFilter> _filters;
public ReceivingCrawledUri(Uri uri)
: this(uri, Enumerable.Empty<IUriFilter>().ToArray())
{ }
public ReceivingCrawledUri(Uri uri, params IUriFilter[] filters)
{
_filters = filters;
CrawlAsync(uri).Start();
}
protected override IDisposable SubscribeCore(IObserver<Uri> observer)
{
return _subject.Subscribe(observer);
}
private async Task CrawlAsync(Uri uri)
{
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromMinutes(1) })
{
IEnumerable<Uri> result = new List<Uri>();
try
{
string html = await client.GetStringAsync(uri);
result = CQ.Create(html)["a"].Select(i => i.Attributes["href"]).SafeSelect(i => new Uri(i));
result = Filter(result, _filters.ToArray());
result.ToList().ForEach(async i =>
{
Interlocked.Increment(ref _numberOfLinksLeft);
_subject.OnNext(i);
await CrawlAsync(i);
});
}
catch
{ }
if (Interlocked.Decrement(ref _numberOfLinksLeft) == 0)
_subject.OnCompleted();
}
}
private static List<Uri> Filter(IEnumerable<Uri> uris, params IUriFilter[] filters)
{
var filtered = uris.ToList();
foreach (var filter in filters.ToList())
{
filtered = filter.Filter(filtered);
}
return filtered;
}
}
public IObservable<Uri> Crawl(Uri uri)
{
return new ReceivingCrawledUri(uri, new ExcludeRootUriFilter(uri), new ExternalUriFilter(uri), new AlreadyVisitedUriFilter());
}
public IObservable<Uri> Crawl(Uri uri, params IUriFilter[] filters)
{
return new ReceivingCrawledUri(uri, filters);
}
}
你可以按如下方式使用它:
Crawler crawler = new Crawler();
IObservable observable = crawler.Crawl(new Uri("http://www.codinghorror.com/"));
observable.Subscribe(onNext: Console.WriteLine,
onCompleted: () => Console.WriteLine("Crawling completed"));
【讨论】: