1. 先安装 scrapy,有虚拟环境的进入虚拟环境安装
pip install scrapy
2. 进入项目总目录(例如文件夹a),如果有虚拟环境,先启动
source ~/.bash_profile
3. 进入虚拟环境
workon xxx
4. 建立一个 scrapy 项目
scrapy startproject scrapy_test
5. 按照提示
cd scrapy_test
scrapy genspider csdn csdn.net如上的话,会在spiders文件下,帮我们自动建立一个 example.py的文件。 我们也可以手动去写这个文件。2种方法都可以。
新建的文件代码如下
import scrapy
class CsdnSpider(scrapy.Spider):
name = 'csdn'
allowed_domains = ['csdn.net']
start_urls = ['http://csdn.net/']
def parse(self, response):
pass
6. 怎么运行爬虫呢? 在命令行中运行如下(注意要在目录和虚拟环境中)
scrapy crawl csdn(上一步建立的爬虫名称)
启动的时候,可能会遇到一个错误, No module named 'win32api'
如果是 widows里面,去下载依赖包, https://www.lfd.uci.edu/~gohlke/pythonlibs/
如果是 mac和linux, 直接 pip install PyWin32 即可
7. 运行是可以了,那怎么写入代码逻辑呢?答,我们要把逻辑写到 csdn.py 中的 parse方法中。例如
def parse(self, response): print("已经下载到html")
如上,我们在命令行中,再启动一次如下代码,就可以看到命令行中有输出 “已经下载到html”
scrapy crawl csdn(上一步建立的爬虫名称)
8. 如上,每一次启动都在命令行中比较麻烦,我们来改进一下。我们目录下,新建一个文件 main.py,文件位置如下
注意main.py 的位置,然后再写入代码,要借助 scray.cmdline 的命令行方法,执行命令行
from scrapy.cmdline import execute # 命令行方法
# 如下命令可以执行命令行
execute(["scrapy", "crawl", "csdn"])
9. 大概参数讲解
allowed_domains = ['csdn.net'] start_urls = ['http://csdn.net/']
- allowed_domains 是被允许爬取的地址,如果我们的页面页面中有外链到其他网站了,如果没有在这里标注,也不被允许爬取
- start_urls 这里是要所有要爬取的地址,我们可以写在其中,到时候scrapy会循环,然后parse是执行逻辑。
10. 在 parse 中写入逻辑,我们之前是用下面的方法
def parse_list(url): res_text = requests.get(url).text # 获取url的内容 sel = Selector(text=res_text) # 获取selector对象 # 我们找的信息,进过观察在 table下面的 tr中 all_trs = sel.xpath("//table[@class='forums_tab_table']/tbody//tr")
现在在parse中不需要这么写了,sel直接就是response,scrapy已经帮我们做好了解析了
def parse(self, response): sel = response # 我们找的信息,进过观察在 table下面的 tr中 all_trs = sel.xpath("//table[@class='forums_tab_table']/tbody//tr")
11. 改造之前的代码,之前用到 domain = "https://bbs.csdn.net", 改造如下
class CsdnSpider(scrapy.Spider):
name = 'csdn'
allowed_domains = ['csdn.net']
start_urls = ['https://bbs.csdn.net/forums/ios']
domain = "https://bbs.csdn.net"
下面parse中用到的domain都改成 self.domain
12. 之前还用到了,保存到数据库,我们在目录下新建一个 models.py 文件,注意目录位置,把之前代码拷贝进去
然后在 csdn.py 中引入
from scrapy_test.models import *
13. 我们在写获取列表的过程中,拿到帖子的连接,是要继续下载帖子的html然后解析的。如下
# 解析帖子内容页面
parse_topic(topic_url)
在scrapy中要改造成,如下,scrapy用yield这种写法,会自动让我们解析列表,然后把下载之后的页面给到我们自己定义的callback函数中,我们只需要专注页面的解析。
# 解析帖子内容页面
yield scrapy.http.Request(url=topic_url, callback=self.parse_topic)
url表示要下载的url, callback表示下载成功之后,进行的操作。我们在parse同级建立一个 parse_topic函数,调用的时候,注意要用,self.parse_topic, (注意函数位置),如下
14. 解析列表页,里面有一个判断是否下一页的。代码如下:
# 如果下一页存在,就取到下一页的连接,放入 next_url
if next_page:
next_url = parse.urljoin(self.domain, next_page[0])
# 继续解析刚刚得到的下一页的地址
parse_list(next_url)
需要改造成,代码如下
# 如果下一页存在,就取到下一页的连接,放入 next_url
if next_page:
next_url = parse.urljoin(self.domain, next_page[0])
# 继续解析刚刚得到的下一页的地址
yield scrapy.http.Request(url=next_url, callback=self.parse)
因为这个方法放在 parse中,所以这个callback也可以不写,默认回调就是 parse方法
# 如果下一页存在,就取到下一页的连接,放入 next_url
if next_page:
next_url = parse.urljoin(self.domain, next_page[0])
# 继续解析刚刚得到的下一页的地址
yield scrapy.http.Request(url=next_url)
15. 继续改造解析内容的部分,原来代码如下
def parse_topic(self, response):
# 解析topic详情页
# 获取帖子的详情以及回复
topic_id = url.split('/')[-1]
res_text = requests.get(url).text
sel = Selector(text=res_text)
这里的topic_id 是从url中得到的,这里改造之后,url 可以从 response.url 获取,如下
def parse_topic(self, response):
# 解析topic详情页
# 获取帖子的详情以及回复
url = response.url
sel = response
如上,sel=response,是因为之前代码下面都用到了sel,所以直接赋值,也可以不赋值,将下面的sel都改成 response 也可以。其他的改造如之前一样。
16. 爬取的过程中,发现要规避 网站的 robots 协议,我们在 scrapy下面的settings.py 中,将 ROBOTSTXT_OBEY改成False
17. 代码写到这里,我们把解析和保存的功能都写在 parse中,scrapy本身还给我们提供了 items.py 和 pipelines.py ,想做到parse中,直解析数据,保存数据这些功能分离出来。
第一步,我们先在 items.py 中新建类,这里是从原来的models中,复制过来,然后把类型都改成 scrapy.Field()类型
class TopicItem(scrapy.Item):
itle = scrapy.Field() # 标题
content = scrapy.Field() # 内容
id = scrapy.Field() # id
author = scrapy.Field() # 作者˚
create_time = scrapy.Field() # 创建时间
answer_nums = scrapy.Field() # 回复数量
click_nums = scrapy.Field() # 点击数量
praised_nums = scrapy.Field() # 点赞数
jtl = scrapy.Field() # 结帖率
score = scrapy.Field() # 赏分
status = scrapy.Field() # 状态
last_answer_time = scrapy.Field() # 最后回复时间
第二步,在csdn.py主流程中引入
第三步,将如下代码修改为
topic = Topic()
修改为
topic_item = TopicItem()
然后将原来赋值的语句,如下
topic.status = status
修改为
topic_item['status'] = status
修改好之后,将原来的保存代码去掉,这个保存的工作放到 pipelines.py 中去做。然后在原来保存代码的地方,yield出去,如下
yield topic_item
ps: parse中,可以yield 2种类型,一种是 yield scrapy.http.Request() 做解析新页面用的,一种是 yield item类型,如果是第二种,则会进入pipelines.py 中进行处理。
那么,怎么在pipelines.py 中接收到 yield过来的值呢,这里要在 settings.py 中设置一下,在 settings.py 中搜索 pipelines.py中的ScrapyTestPipeline,如下代码,把注释去掉
ITEM_PIPELINES = {
'scrapy_test.pipelines.ScrapyTestPipeline': 300,
}
18. 上面说了,yield item类型之后,会自动到 pipelines.py 中处理,所有的 yeild都会进入,那怎么区分呢,有2种方法,我们可以在items.py中,做如下配置
第一种方法,如下,在Item下面新建 to_model方法,然后在 pipelines.py 中调用即可(未测试)
第二种方法,把to_model修改成 save()方法,把整个逻辑放在这里处理
先在 items.py中引入 from scrapy_test.models import * 然后如下:
import scrapy
from scrapy_test.models import *
class ScrapyTestItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
class TopicItem(scrapy.Item):
title = scrapy.Field() # 标题
content = scrapy.Field() # 内容
id = scrapy.Field() # id
author = scrapy.Field() # 作者˚
create_time = scrapy.Field() # 创建时间
answer_nums = scrapy.Field() # 回复数量
click_nums = scrapy.Field() # 点击数量
praised_nums = scrapy.Field() # 点赞数
jtl = scrapy.Field() # 结帖率
score = scrapy.Field() # 赏分
status = scrapy.Field() # 状态
last_answer_time = scrapy.Field() # 最后回复时间
def save(self):
topic = Topic()
topic.title = self['title']
topic.content = self['content']
topic.id = self['id']
topic.author = self['author']
topic.create_time = self['create_time']
topic.answer_nums = self['answer_nums']
topic.click_nums = self['click_nums']
topic.praised_nums = self['praised_nums']
topic.jtl = self['jtl']
topic.score = self['score']
topic.status = self['status']
topic.last_answer_time = self['last_answer_time']
existed_topics = Topic.select().where(Topic.id == topic.id)
if existed_topics:
topic.save()
else:
topic.save(force_insert=True)
之后在 pipelines.py 中,调用 save(), 然后再 return item
class ScrapyTestPipeline(object):
def process_item(self, item, spider):
item.save()
return item
19. 我们在调试的过程,发现一个错误,items.py中,赋值的时候,有些值当时没有获取到,如下
topic.answer_nums = self.['answer_nums']
要修改成如下, get方法的话,就算取不到,也会设置一个默认值
topic.answer_nums = self.get('answer_nums', 0)
20. User-Agent 添加到 scrapy中, 将如下代码
yield scrapy.http.Request(url=topic_url, callback=self.parse_topic)
修改成如下
from fake_useragent import UserAgent
ua = UserAgent()
request = scrapy.http.Request(url=topic_url, callback=self.parse_topic)
request.headers.setdefault("User-agent", ua.random)
yield request
如上写法,是可以的,但是比较繁琐,我们是想将所有的请求都带上 user-agent, 那么操作如下
我们在 middlewares.py 中,加上如下代码
from fake_useragent import UserAgent
class RandomUserAgentMiddleware(object):
def process_request(self, request, spider):
ua = UserAgent()
request.headers.setdefault("User-Agent", ua.random)
这个时候要注意,这个是我们自己加的,所以要把 RandomUserAgentMiddleware 配置到 settings.py 中,大概在55行左右,把注释打开,然后替换我们自己写的类,如下
DOWNLOADER_MIDDLEWARES = {
'scrapy_test.middlewares.RandomUserAgentMiddleware': 543,
}
后面的数字是优先级,越小越先执行,如上的话,就是统一设置了,所有的请求html都会加上 user-agent这个属性
21. 那么怎么设置代理ip呢?
还是在 中间件 middlewares.py 中 加入如下, 这个代码阿布云上面有参考
import base64
# 代理服务器
proxyServer = "http://http-dyn.abuyun.com:9020"
# 代理隧道验证信息
proxyUser = "H01234567890123D"
proxyPass = "0123456789012345"
proxyAuth = "Basic " + base64.urlsafe_b64encode(bytes((proxyUser + ":" + proxyPass), "ascii")).decode("utf8")
class ProxyMiddleware(object):
def process_request(self, request, spider):
request.meta["proxy"] = proxyServer
request.headers["Proxy-Authorization"] = proxyAuth
然后在 settings.py 中 加入这个自定义的类
DOWNLOADER_MIDDLEWARES = {
'scrapy_test.middlewares.RandomUserAgentMiddleware': 543,
'scrapy_test.middlewares.ProxyMiddleware': 542,
}