第二个爬虫
[TOC]
目标
实现爬虫的完整运行,登陆,js解析,url去重,通过中间件进行功能扩展,考虑验证码破解,页面更新
js解析,可以考虑Pyv8,PythonWebKit,Selenium,PhantomJS,Ghost.py等
数据存储,考虑用mongodb
去重,考虑用BitVector
完整的爬取
scrapy里,先定义了starturl,然后parse函数会自动去读取这些url,并做解析
可以通过request
Request(item['href'],callback=self.parescontent)
Scrapy uses Request and Response objects for crawling web sites.
Typically, Request objects are generated in the spiders and pass across the system until they reach the Downloader, which executes the request and returns a Response object which travels back to the spider that issued the request.
模拟登陆
def start_requests(self):
return [Request("https://www.zhihu.com/login", callback = self.post_login)] #重写了爬虫类的方法, 实现了自定义请求, 运行成功后会调用callback回调函数
#FormRequeset
def post_login(self, response):
print 'Preparing login'
#下面这句话用于抓取请求网页后返回网页中的_xsrf字段的文字, 用于成功提交表单
xsrf = Selector(response).xpath('//input[@name="_xsrf"]/@value').extract()[0]
print xsrf
#FormRequeset.from_response是Scrapy提供的一个函数, 用于post表单
#登陆成功后, 会调用after_login回调函数
return [FormRequest.from_response(response,
formdata = {
'_xsrf': xsrf,
'email': '123456',
'password': '123456'
},
callback = self.after_login
)]
配置代理头
调用页面解析时候可以指定header头,requset的定义如下
class Request(object_ref):
def __init__(self, url, callback=None, method='GET', headers=None, body=None,
cookies=None, meta=None, encoding='utf-8', priority=0,
dont_filter=False, errback=None):
self._encoding = encoding # this one has to be set first
self.method = str(method).upper()
self._set_url(url)
self._set_body(body)
assert isinstance(priority, int), "Request priority not an integer: %r" % priority
self.priority = priority
assert callback or not errback, "Cannot use errback without a callback"
self.callback = callback
self.errback = errback
self.cookies = cookies or {}
self.headers = Headers(headers or {}, encoding=encoding)
self.dont_filter = dont_filter
self._meta = dict(meta) if meta else None
实例化Request的时候,可以直接指定header
比如Request('http://www.baidu.com',callback=self.parescontent, method='GET', headers=sth, encoding='utf-8')
如果要配置多个header,并且自动配置也很简单,方式如下
import random
headers = [headera,headerb,headerc]
Request('http://www.baidu.com',callback=self.parescontent, method='GET', headers=random.choice(headers) , encoding='utf-8')
布隆过滤器及改进
布隆过滤器的思想 -- 不追求完美
在quora上,有个问题问,人们最常犯的错误是哪些,其中一个就是追求完美。
在it领域,为了让系统完美化,比如之前涉及存储系统,去重时想达到完美的标准,花的代价是2小时,如果稍加改动,可以让代价降低到1分钟左右,只有本来的百分之一不到。
布隆过滤器的思想,也是如此。
布隆过滤器的应用 - 使用案例
squid
squid里的cache digests 用的是布隆过滤器
chrom
chrom里判断恶意链接,也是用的布隆过滤器
hbase里也用了bloom filter
如图
bloom filter在hbase里的用法比较有意思,它先判断大小,再做bf,这样能让查询速度加快几十倍
布隆过滤器的缺点和改进
缺点
布隆过滤器的缺点是错判,就是说,不在里面的,可能判断成在里面,但是在里面的,一定不会错,而且,无法删除
改进
改进1 多bit
bloom filter其实就是bitmaq的改进,bitmap是单个hash,而bf是多个hash,本质上,都是一个bit只能存有或者没有两种状态
可以考虑用多bit记录的方式,这种方法,就是本来每个bit不是0就是1,现在可以记录其它的,如果add一个元素,把对应bit的数字加1即可
如果要del一个元素,对应位置的数字减1即可
好处是,可以删除元素
缺点是,可能会有位溢出,另外,错判还是会发生,速度也慢了
改进2 白名单
还有种改进方式是对一些常见的url加到白名单里
这种改进是不错的选择,对于某些不考虑过滤的url,可以先判断一下,剩下的url错判就错判,对结果影响是可以接受
布隆过滤器的细节 - 算法的实现
下面用pybloom演示一下布隆过滤器的用法
from pybloom import BloomFilter
from pybloom import benchmarks
f = BloomFilter(capacity=100000, error_rate=0.1)
# [f.add(x) for x in range(102)]
[f.add(x) for x in range(1001)]
for x in range(1001, 100000000):
if x in f:
print x
可以看出,布隆过滤器,还是比较高效的一种数据结构
存储
先安装monogdb,创建一个库叫lei,再建个表,叫做questions
然后在setings.py中配置mongodb的ip,port,db name,collection name这些
ITEM_PIPELINES = {
'tutorial.pipelines.MongoDBPipeline': 300,
}
MONGODB_SERVER = "192.168.100.110"
MONGODB_PORT = 27017
MONGODB_DB = "lei"
MONGODB_COLLECTION = "questions"
# DOWNLOAD_DELAY = 5 # 抓取的延迟
然后建一个pipelines.py,这个pipelines是用来处理item的,把item解析后存到mongodb里,代码也很简单
import pymongo
from scrapy.conf import settings
from scrapy.exceptions import DropItem
from scrapy import log
class MongoDBPipeline(object):
def __init__(self):
connection = pymongo.MongoClient(
settings['MONGODB_SERVER'],
settings['MONGODB_PORT']
)
db = connection[settings['MONGODB_DB']]
self.collection = db[settings['MONGODB_COLLECTION']]
def process_item(self, item, spider):
for data in item:
if not data:
raise DropItem("Missing data!")
self.collection.update({'url': item['url']}, dict(item), upsert=True)
log.msg("Question added to MongoDB database!",level=log.DEBUG, spider=spider)
return item
执行scrapy crawl 51job
,在robomon里即可看到爬取的结果
{
"_id" : ObjectId("57cfcebc2e45cfeb955b5483"),
"url" : "http://jobs.51job.com/nanjing-xwq/81462838.html?s=0",
"job_pay" : "6000-7999/月",
"company_type" : "民营公司",
"company_scale" : "50-150人",
"company_industry" : "影视/媒体/艺术/文化传播",
"job_describe" : "现招一名完美的男孩他要坐立笔直,言行端正。他要行动迅速,不出声响。他可以在大街上吹口哨,但在该保持安静的地方不吹口哨。他看起来要精神愉快,对每个人都笑脸相迎,从不生气。他要礼貌待人,尊重女人他愿意说一口纯正的普通话,而不是家乡话。他在与女孩的相处中不紧张。他和自己的母亲相处融洽,与她的关系最为亲近。他不虚伪,也不假正经,而是健康,快乐,充满活力。",
"job_title" : "现招一名完美的男孩 投资理财顾问助理总助"
}
遇到的问题
beautifulsoup解析页面,结果缺失的问题
在解析http://search.51job.com/list/070200,070201,0000,00,9,99,%2B,2,1.html
时,这个页面的url一直解析不出来所有的url
一直以为是我代码写的有问题,坑了一上午,发现是解析器的问题 fire :-(
如果用bs4,载入时候html5不行,就换html,soup = BeautifulSoup(temp, "html.parser")
运行时报错
提示 AttributeError: 'list' object has no attribute 'iteritems'
原因是在settings文件里,配置item pipelines的时候,没有指定顺序,正确的方式如下:
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300,
'myproject.pipelines.JsonWriterPipeline': 800,
}
items是根据数字大小,从低到高通过pipelines,数字的范围是0-1000