更多优质内容
请关注公众号

爬虫进阶之Scrapy(五) 爬取jobbole伯乐网文章-张柏沛IT博客

正文内容

爬虫进阶之Scrapy(五) 爬取jobbole伯乐网文章

栏目:Python 系列:scrapy爬虫系列 发布时间:2019-12-26 14:39 浏览量:2335

本文通过爬取jobbole伯乐网讲解scrapy爬虫的三个使用小技巧,包括如何编写脚本执行爬虫,在pipeline使用twisted异步数据入库以及在item容器中预处理爬取到的字段。


爬取的域名是:
blog.jobbole.com

首先,在settings,py中将
ROBOTSTXT_OBEY = True
改为False
或者如果他是注释掉的,要将他的注释去掉,再改为False,否则他就会去查看每个网页是否有robots.txt协议,百度就是有这个东西,并在协议中告知禁止爬取,所以scrapy无法爬取百度。
只要我们把这项改为False他就不会查看这个东西,就能爬取。

建立了jobbl这个项目。
然后cmd进入到这个项目的spiders目录
scrapy genspider jobbole blog.jobbole.com

就会在spiders目录创建一个spider文件
命令格式:
scrapy genspider 蜘蛛名 域名

接下来是一个很重要的小技巧,为了方便调试,我们不在cmd执行scrapy crawl 
而是写一个main.py文件(放在和scrapy.cfg同级目录),并在这个文件中执行。


main.py 如下:

from scrapy.cmdline import execute
import sys,os

run_path=os.path.dirname(os.path.abspath(__file__))
sys.path.append(run_path)
execute(["scrapy","crawl","jobbole"])


解释:
execute()方法的作用是模拟cmd命令行执行命令,用空格隔开的每一项就作为execute参数列表的一个元素。
由于scrapy crawl jobbole这个命令必须要在项目目录jobbole下运行,所以将这个路径添加到sys中。
os.path.abspath(__file__)可以获取当前main.py的绝对路径,包含文件名,然后os.path.dirname是获取路径的目录名

在运行main.py之前,在jobboleSpider.py中的def parse()的下一行打一个断点(点击左侧行号即可),然后用alt+shift+F9运行main.py(这个是以调试模式运行py)
运行了之后,回到蜘蛛文件,用鼠标点击一下parse()中的response变量,再点一下他跳出来的绿色的+号,就可以看到response对象中的所有属性和方法


这里再补充一点xpath语法知识:
//div/a|//ul  这样表示获取所有div下的a元素或者或者所有ul元素。就是都能获取到的意思

还有,有的时候一个元素的class有多个,比如是a b c这3个类名。
如果我写成
//div[@class="b"]
他是无法获取到这个元素的
必须写成
//div[@class="a b c"] 才可以

但是还有一个方法:
//div[contains(@class,"b")]
意思是只要class属性中包含 b 这个字符串即可获取到这个元素了


现在,我们不用xpath的方式爬,而是使用css选择器来爬:
比如:
response.css(".entry h1").extract()
response.css(".entry h1")返回的还是selector列表对象,extract是返回节点列表

如果想要获取里面的文本:
response.css(".entry h1::text").extract()

如果想获取a标签的href可以:
response.css("a::attr(href)").extract()

这里我再说一点小技巧,把信息转为json的时候
json.dumps(info)
如果你不希望里面的中文变成\u0e13b4这样的东西,你可以这样:
json.dumps(info,ensure_ascii=False)


=========================================

项目编写过程如下:
建表

create table jobbole_article(
	`id` int unsigned not null primary key auto_increment,
	`title` varchar(755),
	`content` text,
	`time` varchar(50),
	`url` varchar(300)
);


settings.py配置数据库信息:
DB_CONF={
    "host":"127.0.0.1",
    "user":"root",
    "password":"你的数据库密码",
    "database":"test",
    "charset":"utf8",
    "cursorclass":pymysql.cursors.DictCursor
}
并开启pipeline


接下来是第二个重要的小技巧:在pipeline.py中进行数据异步存储

from twisted.enterprise import adbapi
import pymysql

class JobblPipeline(object):
    def open_spider(self,spider):
        #建立连接池
        self.dbpool=adbapi.ConnectionPool("pymysql",**spider.settings.get('DB_CONF'))


    def close_spider(self,spider):
        pass

    def do_insert(self,cursor,item):
        sql = "insert into jobbole_article values (%s,%s,%s,%s,%s)"
        cursor.execute(sql,(None,item['title'],item['content'],item['time'],item['url']))

    def process_item(self, item, spider):
        query = self.dbpool.runInteraction(self.do_insert,item)
		query.addErrback(self.handle_err,item)
		
	def handle_err(self,e,item):
		print(e)



spider如下:

# -*- coding: utf-8 -*-
import scrapy
from jobbl.items import JobblItem
from scrapy.loader import ItemLoader

class JobboleSpider(scrapy.Spider):
    name = 'jobbole'
    allowed_domains = ['blog.jobbole.com']
    start_urls = ['http://blog.jobbole.com/all-posts/']

    def parse(self, response):
        #获取下一页
        page_url = response.xpath("//div[@class='navigation margin-20']/a[contains(@class,'next')]/@href").extract_first()
        if page_url:
            yield scrapy.Request(page_url)

        #获取列表页
        detail_urls=response.xpath("//div[@class='post-meta']//a[@class='archive-title']/@href").extract()
        for detail_url in detail_urls:
            yield scrapy.Request(detail_url,callback=self.parseDetail)

    def parseDetail(self,response):
        #创建ItemLoader
        item_loader=ItemLoader(item=JobblItem(),response=response)
        item_loader.add_xpath('title',"//div[@class='entry-header']/h1/text()")
        item_loader.add_xpath('content',"//div[@class='entry']")
        item_loader.add_xpath('time',"//div[@class='entry-meta']/p[@class='entry-meta-hide-on-mobile']/text()")
        item_loader.add_value('url',response.url)

        #解析xpath
        item=item_loader.load_item()

        yield item


我们在item中对各个字段做预处理:

import scrapy,re
from scrapy.loader.processors import MapCompose,TakeFirst

def strip_space(value):
	#去掉两边的·和空格和换行符
    return value.strip("· \r\n")

def strip_origin(value):
	#正则替换掉<div class="copyright-area">...</div>标签
    pattern = re.compile(r"<div class=\"copyright-area\">.*?</div>")
    return re.sub(pattern,"",value)

class JobblItem(scrapy.Item):
    title = scrapy.Field(
        output_processor=TakeFirst()
    )
    content = scrapy.Field(
        input_processor=MapCompose(strip_origin),
        output_processor=TakeFirst()
    )

    url = scrapy.Field()
    time = scrapy.Field(
        input_processor = MapCompose(strip_space),
        output_processor = TakeFirst()
    )


这里我要说一下,字段获取到之后他会先经过JobblItem的处理之后再返回给蜘蛛,也就是说,spider中的parseDetail中的 return item 中的item是已经处理好了的字段。


PS:回调函数必须放在MapCompose调用前定义,例如上面的strip_space就要放在最上面就定义好.否则会报错

因为我们每一个字段都要经过output_processor=TakeFirst()处理,所以我们可以这样:

在items.py中定义一个类继承ItemLoader类,在这个类中重写一个属性default_output_processor = TakeFirst()

from scrapy.loader import ItemLoader
JobblItemLoader(ItemLoader):
	default_output_processor = TakeFirst()

JobblItem(scrapy.item):
	title = scrapy.Field(
		input_processor = MapCompose(xxx)
	)
	...


这样就不用每一个字段都写output_processor = TakeFirst()这一句了。

然后在spider里面引入JobblItemLoader,实例化容器只需
from jobbl.items import JobblItemLoader,JobblItem
...
item_loader = JobblItemLoader(item=JobblItem(),response=response)
    


总结一下:

1. 在scrapy项目的根目录下编写main.py脚本,通过运行该脚本执行蜘蛛,可以避免在命令行中运行蜘蛛的麻烦而且方便调试

2.在pipeline将数据入库的时候,使用twisted.enterprise.adbapi建立数据库连接池,将数据入库异步化可以提高爬虫的爬取速度,不必等待数据入库这个过程和时间

3.使用scrapy.loader.ItemLoader和scrapy.loader.processors实现数据处理,将这一过程从spider文件和pipeline文件分离,使代码看起来更加简洁清晰有层次





更多内容请关注微信公众号
zbpblog微信公众号

如果您需要转载,可以点击下方按钮可以进行复制粘贴;本站博客文章为原创,请转载时注明以下信息

张柏沛IT技术博客 > 爬虫进阶之Scrapy(五) 爬取jobbole伯乐网文章

热门推荐
推荐新闻