爬取百度百科[scrapy启发]
摘要:主要是基于业务的需要,要一批词,学习了scrapy,借鉴scrapy的一点点思想,写了一个临时爬虫。
一开始,是采用scrapy来写的,可是对于一个框架不熟悉,需要要花时间学习;还有一个主要的,好像代码并不会因为用了这个框架少了多少,可能抓取大量的会有优势。还有一个,我的研究业务单一,就是想要一批词,并且现在就想要,来做一个研究。还有一个, scrapy的异常机制还未找到怎么应用,当出现一些错误它就运行停止了,受制于scrapy。如果把它的源码看完估计用起它会好很多,因为网上的资料都是基础的入门篇,什么代理,什么头配置,什么cookie,什么异常处理都要分别查找。在尝试scrapy之后,未果,不过可把它思想搬过来用一下,然后写了一个临时的任务。
1. 实体类
定义实体类,其实这个是字典,就两个字段,一个词,一个是url;
class BaiduBaikeItem(scrapy.Item):
keyword = scrapy.Field()
url = scrapy.Fiel)
2. spider
在写scrapy的时候,也用到了yield这个东西,这个是生成器,当程序运行到yield语句时,会返回到next或send或for循环或异常那里的。这个可以查看上一篇《python[变量作用域-函数-闭包-装饰器-生成器]》。
还有一个细节,这里用到一个from urllib.parse import urljoin函数,这个函数可以帮助我们节省很多拼url的代码,这个是在scrapy中发现的。
对于选择器的解释,scrapy里面封装了一个,这里采用了from bs4 import BeautifulSoup这个,这个东西对于HMTL标签解释挺强大的。
对于url请求,采用了requests这个包,它可请求get,post等http协议请求。
保存是把字典转成json,然后把json采用codecs来写文件。
# coding=utf-8
import codecs
import json
import random
import time
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
from items import BaiduBaikeItem
from crawDataWithProxy import proxy_handler
# 根列表
cataUrls = {
'http://baike.baidu.com/fenlei/%E5%81%A5%E5%BA%B7%E7%A7%91%E5%AD%A6': '健康科学',
}
baikeHeaders = {
'Host': 'baike.baidu.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0',
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'Accept-Encoding': 'gzip,deflate',
'Cache-Control': 'no-cache',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': '0',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
}
# 是否使用代理
proxy = False
# 重爬次数
M = 10
COUNT = 0
# get response
def r_get(url, isproxy=False):
time.sleep(random.random() * 0.5)
r = None
try:
if not isproxy:
r = requests.get(url, timeout=20, headers=baikeHeaders)
else:
r = requests.get(url, timeout=20, proxies=proxy_handler, cookies=None)
except Exception as e:
print(e)
if (r is not None and r.status_code != 200):
r = None
return r
# 重试
def retry(n, url):
s = random.random() * (2 * n) + 1
print('%dth times, %f秒后重试。' % (n, s))
time.sleep(s)
response = r_get(url, isproxy=proxy)
return (response, n + 1)
# 解释,这个函数用来递归
def myparse(url, page=0, catalog=1):
global COUNT
n = 0
page += 1
if page == 1:
print('##########(catalog:%s(%s),page:%d)#(total itmes:%d)###########' % (cataUrls[url], url, page, COUNT))
else:
print('##########(catalog:%d,page:%d(%s)#(total items:%d)###########' % (catalog, page, url, COUNT))
response = r_get(url, isproxy=proxy)
# 当抓取失败后,可进行多次抓取
while True:
if response == None:
rs = retry(n, url)
response = rs[0]
n = rs[1]
if n < M:
continue
else:
print('已经进行了%d次的重试..%s' % (M, url))
break
else:
response.encoding = 'utf-8'
htmls = response.text
base = response.url
htmlbf = BeautifulSoup(htmls, "lxml")
i = 0
try:
itemlist = htmlbf.select(".grid-list > ul > li > div.list > a")
except Exception as e:
print(e)
itemlist = []
# 对于空情况,要考虑是否重试
if len(itemlist) == 0:
if u'百度百科错误页' in htmlbf.text:
print(u'百度百科错误页...')
response = None
continue
else:
print('%s request has no items.' % (url))
else:
# 解释每个关键词
for quote in itemlist:
b_item = BaiduBaikeItem()
b_item['keyword'] = quote.text
b_item['url'] = urljoin(base, quote['href'])
i += 1
print('count of item:%d' % (i))
# 产生item,这个跳出去让这个item进行保存起来,这个scrapy也是这样实现的
yield b_item
COUNT = COUNT + i
# 找到下一页的网址,让它递归就可以了
print('# 下一页')
try:
next_sr = htmlbf.select('#next')
next_page_url = next_sr[0]['href']
except Exception as e:
print('下一页 Exception %s' % (e))
next_page_url = None
if next_page_url is not None:
print("准备下一页")
# 这个算是一个生成器嵌套着一个生成器了,虽然是自已调用了自己
g = myparse(url=urljoin(base, next_page_url), page=page, catalog=catalog)
for item in g:
yield item
else:
pass
# 如果是第一页的时才会从分类的目录进行前进,找到下一级目录的URL,让它递归
if page == 1:
print("准备下一级目录")
try:
categoryx = htmlbf.select(".p-category > div > span")[0].text
except Exception as e:
print('has no 下一级目录,e = %s, url = %s' % (e, url))
categoryx = None
if categoryx is not None and u'下级分类' in categoryx:
next_sr = htmlbf.select(".p-category > div > a")
for next_catalog in next_sr:
c_url = next_catalog['href']
txt = next_catalog.text
if c_url is not None:
whole_url = urljoin(base, c_url)
# 排除重复的url
if whole_url not in cataUrls:
cataUrls[whole_url] = txt
c_item = BaiduBaikeItem()
c_item['keyword'] = txt
c_item['url'] = whole_url
# 希望对这个分类也保存一下
yield c_item
# 开始下一个分类
catalog += 1
print('will claw (%s:%s)' % (txt, c_url))
# 嵌套生成器,遍历时就调用了这个生成器
g = myparse(url=whole_url, page=0, catalog=catalog)
for item in g:
yield item
else:
print('(%s,%s) has be crawed..will skip it ' % (txt, c_url))
else:
print('this is not 下级分类, skip...')
else:
pass
break
if __name__ == '__main__':
file = codecs.open('%s.json' % ('baike'), 'w+', encoding='utf-8')
# 这里要用一个副本做存出来,不然后说cataUrls是变的提示
start_urls = list(cataUrls.keys())
for url in start_urls:
g = myparse(url)
for item in g:
# print(item)
line = json.dumps(dict(item), ensure_ascii=False) + "\n"
file.write(line)
file.flush()
file.close()
for k, v in cataUrls.items():
print(k, v)
3. 代理格式
可以设置一下请求代理,格式如下,像上面那样引用就可以了。
proxyHost = "地址"
proxyPort = "端口"
proxyUser = "用户名"
proxyPass = "密码"
proxyMeta = "http://%(user)s:%(pass)[email protected]%(host)s:%(port)s" % {
"host": proxyHost,
"port": proxyPort,
"user": proxyUser,
"pass": proxyPass,
}
proxy_handler = {
"http": proxyMeta,
"https": proxyMeta,
}
本试探性研究到这里,仅作为学习研究参考,才疏深浅,请指教。。
【作者:happyprince, http://blog.csdn.net/ld326/article/details/78757223】
上一篇: Linux系统配置Java环境笔记