之前大部分的中间件都是在Docker中做的, 感觉Docker的确是牛逼, 隔离环境. 最近做一个爬虫的项目, 用到Scrapy, 最开始没有往Docker方面想, 之后有空需要研究下Docker如何安装Scrapy
安装
Scrapy是基于Python的爬虫框架, 需要先安装Python, 我的环境是Ubuntu 16.4默认条件下, Ubuntu安装了两个Python, Python2.7和Python3.5, 不知道这是什么操作
目前最新的Scrapy是基于Python3的, 如果在机器上有两个版本的Python, Scrapy会有冲突, 所以最开始先卸载Python2.7
sudo apt-get remove python
scrapy需要通过pip3进行安装, 注意这里是要pip3安装scrapy, 不是用pip, pip是基于python2的
安装pip3
可以使用如下命令安装pip3
sudo apt-get install python3-pip
如果安装过程中出现任何错误, 就多试几遍
查看pip3是否安装成功
pip3 –version
安装scrapy
pip3 install scrapy
安装过程中可能会报错, 并提示更新pip3
You are using pip version 8.1.1, however version 19.3.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
使用如下的命令更新pip3
pip3 install --upgrade pip
继续通过pip3 install scrapy, 出现下面的错误
这是因为将pip3更新为10.0.0后库里面的函数有所变动造成这个问题
nano /usr/bin/pip3
将原来的
from pip import main
if __name__ == '__main__':
sys.exit(main())
改为
from pip import __main__
if __name__ == '__main__':
sys.exit(__main__._main())
直到这里, 再通过pip3安装scrapy, 就基本上不会出错了
通过下面的命令验证scrapy的安装
pip3 list | grep Scrapy
案例
找了很多网上的教程, 发现有一个例子比较适合入门
scrapy startproject 工程名
通过这个命令, scrapy会帮忙建一个例子工程
items.py
这个文件定义了工程中会用到的类
import scrapy
class CrawlsHouseItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
class DanKe(CrawlsHouseItem):
# 房子名称
host_name = scrapy.Field()
# 租金
price = scrapy.Field()
# room list
room_list = scrapy.Field()
pipelines.py文件定义了工程爬虫的输出
第一种方法是输出到文件
import io
import sys
reload(sys)
sys.setdefaultencoding('utf8')
class TutorialPipeline(object):
def process_item(self, item, spider):
return item
class CrawlsHousePipeline(object):
def __init__(self):
self.file = io.open('danke.json', 'a', encoding='utf-8')
def __del__(self):
self.file.close()
def process_item(self, item, spider):
print(item, spider)
self.file.writelines(item['host_name']+'|'+item['price']+'|'+item['room_list'])
return item
第二种方式是输出到数据库
import io
import pymysql
import sys
reload(sys)
sys.setdefaultencoding('utf8')
class TutorialPipeline(object):
def process_item(self, item, spider):
return item
class CrawlsHousePipeline(object):
def __init__(self):
self.conn = pymysql.connect(
host='192.168.25.132',
port=3306,
database='dk',
user='root',
password='123456',
charset='utf8'
)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
sql = ("insert into danke(host_name,price,room_list)"
"values (%s, %s, %s)")
item = dict(item)
data = [item['host_name'],
item['price'],
item['room_list']
]
self.cursor.execute(sql, data)
self.conn.commit()
return item
def close_spider(self, spider):
self.cursor.close()
self.conn.close()
其他还有文件middlewares.py定义了工程中要用到的中间件
文件settings.py定义了工程的设置
真正的爬虫文件在spiders里面
class DankeSpider(scrapy.Spider):
name = 'danke'
allowed_domains = ['www.danke.com']
start_urls = ['https://www.danke.com/room/bj']
def parse(self, response):
for href in response.xpath("//div[@class='r_lbx_cena']//a//@href").extract():
yield scrapy.Request(href, self.parse_item)
for next_href in response.xpath("//div[@class='page']//a//@href").extract():
yield scrapy.Request(next_href)
def parse_item(self, response):
danke = DanKe()
danke['host_name'] = response.xpath('//h1//text()').extract()[0]
price = response.xpath('//div[@class="room-price-sale"]//text()').extract()[0]
danke['price'] = price.replace(" ", "").replace("\n", "")
room_list = []
for room in response.xpath('//div[@class="room-list"]//text()').extract():
room = room.replace(" ", "").replace("\n", "")
if len(room) != 0:
room_list.append(room)
danke["room_list"] = ",".join(room_list)
return danke
分页的抓取
分页的抓取是个比较头痛的部分, 最简单的情况是在代码中会直接有下一个页面的地址
<div class="page">
<a href="https://www.danke.com/room/bj?page=1" class="on">1</a>
<a href="https://www.danke.com/room/bj?page=2" class="">2</a>
<a href="https://www.danke.com/room/bj?page=3" class="">3</a>
<a href="https://www.danke.com/room/bj?page=4" class="">4</a>
<a href="https://www.danke.com/room/bj?page=5" class="">5</a>
<a href="https://www.danke.com/room/bj?page=2"> > </a>
</div>
这样的话, 可以直接在scrapy进行循环获取
for next_href in response.xpath("//div[@class='page']//a//@href").extract():
yield scrapy.Request(next_href)
但是大部分页面的分页都是通过js控制的, 并不会有直接的链接, 这个时候如果只是单一系列的页面, 可以定义全局的变量, 然后拼页面的链接实现
如果是一系列的页面, 不太容易可以给全局变量进行清零, 所以这种方式不合适
另外一种比较灵活的办法, 分页的控件都会有下一页的按钮, 基本上都会传入下一页的页码, 这时候通过解析锚点得到下一页的页码, 再拼页面的链接实现下一页的抓取