# 原理

爬虫原理

流程如下:

  • 爬虫告诉引擎,需要爬取哪些链接 urls
  • 引擎把链接发给调度器
  • 调度器收到链接后,进入队列
  • 调度器从队列里取出链接,告诉下载器开始下载
  • 下载完毕后,把结果交给爬虫
  • 爬虫把结果中取回数据,交给管道
  • 管道将数据存储到 MySQL/文本文件等

# 准备工作

安装 Python 3 后,

pip3 install scrapy

(可选)在本地安装 Redis 数据库,端口 5376,用户名 root,密码 123456;然后安装 Python 调用 Redis 的库:

pip3 install redis

# 简单爬虫

这里针对一个列表的网页爬取其列表内容,示例为 https://mobile.ithome.com/ 的新闻标题、链接等信息。

# 建立目录

第一步,建立项目。

scrapy startproject my_spider D:\  # 建立项目

会建立如下目录:

my_spider/
    scrapy.cfg
    my_spider/
        __init__.py
        items.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            ...

然后可以在 PyCharm 中打开 D:\my_spider

其中,settings.pyROBOTSTXT_OBEY = True 表示会自觉遵守页面规定,如果不让爬虫则不会进行爬虫。

# 生成爬虫代码

第二步,切换目录,然后生成一个爬虫:

cd /d D:\my_spider
scrapy genspider ithome mobile.ithome.com # 生成爬虫

此时 D:\my_spider\my_spider\spiders 下会自动生成 ithome.py,内容如下:

import scrapy

class IthomeSpider(scrapy.Spider):
    name = 'ithome'
    allowed_domains = ['mobile.ithome.com']
    start_urls = ['http://mobile.ithome.com/']

    def parse(self, response): # 解析 (parse) 网页的方法
        pass

这里可以将 start_urls 链接改为 https

# 定义数据结构

第三步,定义数据结构。这里没有直接使用 Python 原生的继承于 objectclass,而是选择继承 scrapy.Item 的类。这样定义的数据结构,更方便 Scrapy 处理。定义放在 D:\my_spider\my_spider\items.py

import scrapy

class IthomeData(scrapy.Item):
    def __init__(self):
        title = scrapy.Field()
        time = scrapy.Field()
        abstract = scrapy.Field()
        url = scrapy.Field()

# 解析网站

第四步,定义数据结构后,开始解析爬虫获得的数据。爬虫本质获得的是 start_urls 对应的 html 文件, 然后解析 html,将解析到的数据进行处理。

我们用 Chrome 打开 https://mobile.ithome.com/,右键一个标题并“检查”,可以看到该 HTML 的结构。

mobile.ithome.com 详情

同时,在命令行执行 scrapy shell https://mobile.ithome.com/。看到以下输出:

2020-07-01 16:55:01 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://mobile.ithome.com/> (referer: None)
2020-07-01 16:55:02 [asyncio] DEBUG: Using proactor: IocpProactor
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x000001AEBC19F0A0>
[s]   item       {}
[s]   request    <GET https://mobile.ithome.com/>
[s]   response   <200 https://mobile.ithome.com/>
[s]   settings   <scrapy.settings.Settings object at 0x000001AEBC19BC70>
[s]   spider     <DefaultSpider 'default' at 0x1aebc4e6730>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
2020-07-01 16:55:02 [asyncio] DEBUG: Using proactor: IocpProactor
In [1]:

200 表示正常。输入 response 并回车:

In [1]: response
Out[1]: <200 https://mobile.ithome.com/>

接下来解析 response。解析的方法是使用 xpath

XPath 即为 XML 路径语言,它是一种用来确定 XML 文档中某部分位置的计算机语言。XPath 基于 XML 的树状结构,提供在数据结构树中找寻节点的能力。

在 Chrome 中右键想要爬取的第一个标题,然后 Copy Xpath,如下图:

获取 Xpath

然后作为 responsexpath 成员函数的参数,执行,结果如下,如果输出不是 [] 则正确。使用 extract_first 看到其内容。

In [12]: response.xpath('//*[@id="wrapper"]/div[1]/div[3]/ul[1]/li[1]/div/h2/a')
Out[12]: [<Selector xpath='//*[@id="wrapper"]/div[1]/div[3]/ul[1]/li[1]/div/h2/a' data='<a href="https://www.ithome.com/0/491...'>]

In [14]: response.xpath('//*[@id="wrapper"]/div[1]/div[3]/ul[1]/li[1]/div/h2/a').extract_first()
Out[14]: '<a href="https://www.ithome.com/0/491/113.htm" target="_blank">小米NFC碰碰贴要回来了?小米高管:大家喜欢的话我们就做</a>'

可以看到,标题已经出来了,并且和前面的图是一致的。

然后尝试找到遍历的方法。html 里,ul 是列表(unordered list),下面的每一个元素为 lilist item)。将上面的 Xpath 从 li 分开,因为我们需要在 Python 中遍历每个元素。注意在 Xpath 中下标从 1 开始,而在 Python 中下标从 0 开始。

In [16]: response.xpath('//*[@id="wrapper"]/div[1]/div[3]/ul[1]/li')[0].xpath('./div/h2/a').extract_first() # 注意第二个 Xpath 的开头有个 .
Out[16]: '<a href="https://www.ithome.com/0/491/113.htm" target="_blank">小米NFC碰碰贴要回来了?小米高管:大家喜欢的话我们就做</a>'
In [17]: response.xpath('//*[@id="wrapper"]/div[1]/div[3]/ul[1]/li')[1].xpath('./div/h2/a').extract_first() # 测试下一篇文章是否也是对应的
Out[17]: '<a href="https://www.ithome.com/0/491/111.htm" target="_blank">荣耀30系列强势霸榜618,蝉联多日销量冠军,立减300仅要2699起</a>'

再进一步获取其文本和链接,使用 text() 函数和 @href

In [18]: response.xpath('//*[@id="wrapper"]/div[1]/div[3]/ul[1]/li')[0].xpath('./div/h2/a/text()').extract_first()
Out[18]: '小米NFC碰碰贴要回来了?小米高管:大家喜欢的话我们就做'
In [36]: response.xpath('//*[@id="wrapper"]/div[1]/div[3]/ul[1]/li')[0].xpath('./div/h2/a/@href').extract_first()
Out[36]: 'https://www.ithome.com/0/491/113.htm'

用同样的方法可以获取其时间、摘要:

In [41]: response.xpath('//*[@id="wrapper"]/div[1]/div[3]/ul[1]/li')[0].xpath('./div/div/p').extract_first()
Out[41]: '<p>小米早前曾发布了一款名为“小米NFC碰碰贴”的产品。今日小米集团智能硬件部总经理刘新宇表示,当时碰碰贴概念太超前,如今大家喜欢,“那我们就做新版碰碰贴”。似乎也在暗示小米NFC碰碰贴产品即将回归</p>'
In [48]: response.xpath('//*[@id="wrapper"]/div[1]/div[3]/ul[1]/li')[0].xpath('./div/h2/span/text()').extract_first()
Out[48]: '今日 17:35'

到这里就已经成功一半了。

# 编写解析函数

parse()spider 的一个方法。 被调用时,每个初始 URL 完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的 URL 的 Request 对象。

回到 D:\my_spider\my_spider\spiders\ithome.py,编写 parse 成员函数。parse 函数的思想就是,遍历 ulli,取出我们需要的信息存进 item,然后返回 item。这里直接上代码。

# ithome.py

import scrapy
from my_spider.items import IthomeData

class IthomeSpider(scrapy.Spider):
    name = 'ithome'
    allowed_domains = ['mobile.ithome.com']
    start_urls = ['https://mobile.ithome.com/']

    def parse(self, response):
        li_list = response.xpath('//*[@id="wrapper"]/div[1]/div[3]/ul[1]/li')
        item_list = []
        for li in li_list:
            item = IthomeData()
            item["title"] = li.xpath('./div/h2/a/text()').extract_first()
            item["url"] = li.xpath('./div/h2/a/@href').extract_first()
            item["abstract"] = li.xpath('./div/div/p').extract_first()
            item["time"] = li.xpath('./div/h2/span/text()').extract_first()
            item_list.append(item)

        return item_list

注意,import 引入 item 的方式有点不一样,这里是 from my_spider.items import IthomeData

# 编写管道

管道则是将上面 parse 函数的解析出来的 item_list 进行处理、存储。这里为简化代码,直接输出;当然也可以写入文本文件、存储到数据库等。代码如下:

# pipelines.py
class MySpiderPipeline:
    def process_item(self, item, spider):
        print(item)

# 运行爬虫

最后的最后,运行爬虫。可以直接在 D:\my_spider\my_spider 目录下运行 scrapy crawl ithome,也可以在该目录下编写以下 python 代码并运行。

from scrapy.cmdline import execute

execute('scrapy crawl ithome'.split(' '))

如果部分输出如下则正常(IT 之家貌似爬虫和直接访问得到的 HTML 不一样?可能做了防爬虫。至少方法是对的)。

{'abstract': '<p>今天小米10 Pro手机正式推送MIUI 12.0.1稳定版更新,并且不是稳定版内测,这意味着小米10 '
             'Pro手机普通用户也可以方便升级MIUI 12.0.1稳定版系统了。</p>',
 'time': '今日 23:09',
 'title': '小米 10 Pro 正式推送 MIUI 12 稳定版',
 'url': 'https://www.ithome.com/0/493/559.htm'}
2020-07-01 18:20:38 [scrapy.core.scraper] DEBUG: Scraped from <200 https://mobile.ithome.com/>
{'abstract': '<p>今日消息人士Jon_Prosser也曝光了代号为C68的苹果AirPower充电板原型机的真机照:此次曝光的AirPower充电板似乎解决了
过热问题,其已可满足AirPods '
             'Pro和一块Apple Watch同时充电</p>',
 'time': '今日 22:37',
 'title': '苹果AirPower充电板原型机曝光!Apple Watch/AirPods可一起充电',
 'url': 'https://www.ithome.com/0/493/555.htm'}