利用Scrapy爬取新浪话题新闻并保存至mysql

相关代码参见:https://github.com/snowcement/Knowledge-Graph-related/tree/master/Spider

爬取页面介绍

  1. 初学爬虫,我也参考了该答案:

  2. 因为要构建一个事理图谱,所以需要先获取构建所需的语料,最后我选取了一个具有某些行业性质的司法新闻作为爬取对象,初始页面地址为http://sifa.sina.com.cn/news/

  3. 通过观察,在左侧大块区域的新闻面板中,有5个tab板块,其中“最新”板块是右侧其余四个板块新闻的汇总,只需爬取“最新”板块中的所有新闻即可。爬取分为两个维度:即“纵向”垂直爬取和“横向”水平爬取。前者是指在“最新”板块中,将板块内的所有新闻利用爬虫进行爬取,后者是指“最新”板块浏览过程中的翻页动作,将每一页的信息爬取下来。

  • 1.png
  1. 对于“纵向”维度,点击任意一条新闻进入,如图所示

  2. 我们需要提取新闻的标题、发布时间、发布来源、正文内容、新闻关键字、新闻的url等信息,如图所示,对这部分解析的代码见“SinaSFNews/SinaSFNews/spiders/news_spider.py”中的parse_item函数:

    • 在该函数中不仅对pc端的新闻进行解析,还对移动端的新闻进行了解析(原因见6)

    • 函数对爬取条数记录在日志中【log_crawl.txt】,便于在服务器运行爬虫时跟踪爬取进度

    • 对爬取对象构建ItemLoader对象,并利用add_xpath,add_value等对其添加感兴趣字段

    • 2.png
  1. 因为大部分网站都进行了反爬措施,我们需要使用随机的用户代理(UA)和随机的IP代理,这部分我参考了 scrapy下载中间件 这篇文章,由于使用了随机的用户代理,虽然爬取网址是PC端的新闻,会进行自动重定向,新闻的url为变为以wap结尾的移动端新闻,所以需要对wap格式的新闻也要做出解析,代理相关代码见“SinaSFNews/SinaSFNews/spiders/middleware.py”中的RandomHttpProxyMiddleware类和RandomUserAgentMiddleware类,它们都属于中间件的一种,下面对这两个类功能进行介绍:

    • RandomHttpProxyMiddleware

      • 需要先对一些免费代理网站进行爬取,获取部分代理IP列表,这里我参照网上现有代码示例对xici【http://www.xicidaili.com】进行爬取,获取部分基于HTTPS的代理IP集合和基于HTTP的代理IP集合,原理就是:使用爬取到的代理再次发送请求到http(s)://httpbin.org/ip,验证代理是否可用,在具体爬取时,我发现已经通过 http(s)://httpbin.org/ip 验证的代理还是会大量出现“返回值异常”【e.g. 返回状态码如404,503等】和“连接异常”【e.g. 返回异常原因如TcpTimeOutError等】等问题,而使用 89免费代理 会更方便,这里不需要爬取了,网站本身提供代理IP检测的服务,在首页将部分国内代理IP输入到"代理IP检测"进行检测,选取可用代理IP即可【最终使用了14个HTTP的代理IP】,将代理IP记录到“proxy_list.json”中便于后续处理,同时将proxy_list.json文件的存放路径在SinaSFNews/SinaSFNews/spiders/settings.py”中进行配置,见HTTPPROXY_PROXY_LIST_FILE字段

      • 该类主要对HTTPPROXY_PROXY_LIST_FILE字段进行加载,获取代理了IP列表,并在每个Request发送前调用_set_proxy方法,在request的meta字典中添加‘proxy’字段并发送

    • RandomUserAgentMiddleware

      • 调用faker包,在每个Request发送前随机生成用户代理,即User-agent信息,并在request的headers中添加User-agent字段,防止反爬
  2. 对于“横向”维度,我们对“最新”板块新闻进行浏览,在滚动到30条新闻左右的位置,页面会进行动态加载出现新的“30条新闻”,接着向下滚动页面,在浏览过60条新闻时又会加载30条新闻,接着滚动到页面底部,显示了点击“下一页”的按钮

    • 在sifa.sina.com.cn/news/index页面中看到id="feedCard"中的代码块为空,并未显示任何新闻,因此该页面是采用了infinite roll方式的ajax动态页面,以chrome浏览器为例,无法根据F12跳出的页面审查页中的Elements的显示内容进行提取【对静态页面,选取感兴趣元素,进行copy xpath】,因此需要查看Sources 中的js代码,分析第1-30条,第31-60条,第61-90条新闻内容是何时被加载进来的

    • 3.png
    • 4.png
    • 如图所示,在index页面中,我们找到了关于左侧板块的相关信息,即tab_-2120,它的url为url: '//feed.mix.sina.com.cn/api/roll/tags?channelid=1&sq=x_where:digit_cl==399872&begin=1401552000&tags=' + encodeURIComponent(''),在左侧Page列表中找到feed.mix.sina.com.cn/api/roll中发现对应的JS文件【get?pageid=354&lid=2120&num=30&versionNumber=1.2.8&page=1&encode=utf-8&callback=feedCardJsonpCallback】,页面向下滚动到第二个30条新闻时,出现了第二个JS文件,可在Network中通过检测JS进行查看,如下图所示

    • Inked03eee8a4b97523f53b990ea92f921073_LI.jpg
    • 第二个JS文件将page=1替换为ctime=xxx_=xxx,通过尝试,发现我们将这两部分信息替换为page=2返回结果是相同的,这意味着我们不需要探究ctime_字段值的生成方式了

    • 最终的“横向”爬取url的信息即:http://feed.mix.sina.com.cn/api/roll/get?pageid=354&lid=2120&num=30&versionNumber=1.2.8&page=1&encode=utf-8&callback=feedCardJsonpCallback,将page=1,依次递增即可,通过尝试发现当page=1487时页面返回为空,故共爬取1486个“横向”页面

  3. 写入mysql数据库,需要在“SinaSFNews/SinaSFNews/spiders/pipelines.py”对相关功能进行写入,利用twisted提供的异步方式多线程访问数据库模块adbapi,提高程序访问数据库的效率,见MySQLAsyncPipeline类,这里使用的pymysql包,注意cursorclass字段设置

遇到的问题

  1. 默认“返回值异常”和“连接异常”等问题,重试3次即退出,由于使用代理IP不是很稳定,3次是不够的

    • 为解决该问题,使用Retry中间件,即middleware.py中的MyRetryMiddleware类,参考了 重写scrapy中间件之RetryMiddleware并对其进行修改,Retry的目的:

      • 增加重试次数

      • 对产生的异常进行捕获,进一步处理,防止因异常造成的程序中断,需要在settings.py中配置一下信息:

        • RETRY_ENABLED、RETRY_TIMES、RETRY_HTTP_CODES
      • 异常捕获后,采取的操作可以是打印异常代理IP,提示异常原因,并进行重试,这里对重试代码进行修改

        • RetryMiddlewareMyRetryMiddleware的基类)的方法_retry()进行封装,而非直接调用,随机更换代理IP后再发起重试
  2. 因为已经在Retry中间件对异常进行捕获,无需其他的异常处理的中间件了

    • 之前为异常处理有单独写了异常处理中间件ProcessAllExceptionMiddleware, 并将其在settings.py中配置的值【设为120】比 MyRetryMiddleware的值【设为100】要高,以“连接异常”为例,导致出现异常时总会先进入ProcessAllExceptionMiddlewareprocess_exception方法,因为异常捕获后只是打印了日志,并未进行重试,往往重试3次仍失败的情况下,程序就异常中断了

    • MyRetryMiddleware的值【设为130】后,问题得到解决,因此异常处理中间件不再需要

  3. 在进行“横向”维度爬取时,除了以start_urls列表中包含的首个页面能够正常爬取,其余的后续页面总会发生大批量的“403Forbidden”信息,而这些链接单独放入浏览器访问时,是有具体返回结果的

    • 因为首个页面可以正常爬取,后续页面却不能,后续页面的调用在news_spider.py中的parse函数,在函数结尾处调用Request(next_url, dont_filter=True, meta=meta)生成器,迭代横向爬取

    • 原因分析:应该是系统首次发送的Request和我调用yield Request请求内容不同,对两个Request内容分别进行截取比对发现,有以下内容不同:

      • 我的Request.headers中增加了Referer字段信息,表示了该页面是从哪个页面来的

      • 我的Request.meta中增加了depth字段信息,表示当前的爬取深度

      • 需要对这两部分多余的信息进行处理,最后在request随机IP代理生成处的_set_proxy方法中添加了过滤代码段,去掉Refererdepth字段信息,修改后,横向爬取“403Forbidden”问题解决了

  4. 爬取一段时间后爬虫关闭(爬取7000+条),失败第一次

  5. 爬取一段时间后爬虫关闭(爬取约9000条),失败第二次

    • 发现在爬取9000条后,引发大量的retry,什么原因?

      • 横向爬取页面【打算爬取共1486个页面】,但在第287个页面时频繁发生失败并重试,达到了最大重试次数RETRY_TIMES,引发ERROR: Error downloading,故后续的横向页面均无法爬取,采取措施:跳过该横向页面,继续访问下一个横向页面,需要自己构造HtmlResponse(url=request.url),根据url地址提取page信息,爬取下一个页面,因为是自己构造的response,其response.txt内容为空,故无法进行纵向爬取,直接跳过,问题解决
    • 横向页面停止爬取后,只能对当前已爬取其未成功的剩余页面进行retry,信息如下:INFO: Crawled 8477 pages (at 0 pages/min), scraped 8191 items (at 0 items/min)。当均达到最大重试次数RETRY_TIMES时,由于直接return,使得有request而没有response,最终引发大量的类似[scrapy.core.scraper] ERROR: Error downloading <GET http://news.sina.com.cn/sf/news/fzrd/2018-11-01/doc-ifxeuwwt0086509.shtml>样的错误,只需要参考之前写过但为使用的异常处理中间件ProcessAllExceptionMiddleware,构造HtmlResponse(url='')并返回即可

总结

  1. 所有的中间件需要在settings.py中配置,见DOWNLOADER_MIDDLEWARES字段

  2. 写入mysql时包含的数据库基本信息需要在settings.py中配置,见MYSQL_*字段和ITEM_PIPELINES字段

  3. 为防止爬虫被识别,可在settings.py在中添加以下字段:

    • DOWNLOAD_DELAY,设为0.25秒即可,一开始设成3,速度略慢

    • DOWNLOAD_TIMEOUT,下载器在超时前等待的时间量(以秒为单位)

    • RANDOMIZE_DOWNLOAD_DELAY,在从同一网站获取请求时等待随机时间,降低了由分析请求的站点检测(并随后阻塞)爬行器的机会

    • CONCURRENT_REQUESTS_PER_DOMAIN,降低并发数量,默认16

    • AUTOTHROTTLE_ENABLED,开启自动限速

    • AUTOTHROTTLE_MAX_DELAY,设置最大下载延时

    • AUTOTHROTTLE_DEBUG,开启【自动限速】的debug

  4. 爬虫写好后,在服务器后端运行时,将命令写入start.sh,在命令行模式调用nohup sh start.sh > my.log &

    • my.log中查看程序运行日志,在log_crawl.txt中查看当前爬取进度

    • my.log中查看程序运行日志,在log_crawl.txt中查看当前爬取进度

    • 如需终止nohup,可调用ps -ef|grep sinasf,获取当前爬虫占用的进程号【第二列对应的数字,e.g. 11887】,调用kill -9 11887

5 用命令行清空数据库中的数据
* mysql -uxxx -pxxx -h'xx.xx.x.xxx'【用户名,密码,部署ip】
* use xxx;【数据库名称】
* show tables;
* delete from xx;【表名】

  • 最终效果图如图:


    捕获.PNG
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容