python3 爬虫新手笔记(一):Beautiful Soup、Scrapy尝试

文章目录

  • python3 爬虫新手笔记(一):Beautiful Soup、Scrapy尝试
    • 一. python爬虫架构
    • 二. Beautiful Soup 介绍
    • 三. Scrapy 介绍
      • 1. 编写Spider
      • 2. Selectors选择器
      • 3. 将数据保存到MySQL
      • 4. Scrapy中的去重
      • 5. 遇到的问题
    • 参考

python3 爬虫新手笔记(一):Beautiful Soup、Scrapy尝试

一. python爬虫架构

Python爬虫介绍|菜鸟教程

Python 爬虫架构主要由五个部分组成,分别是调度器、URL管理器、网页下载器、网页解析器、应用程序(爬取的有价值数据)。

  • **调度器:**相当于一台电脑的CPU,主要负责调度URL管理器、下载器、解析器之间的协调工作。
  • **URL管理器:**包括待爬取的URL地址和已爬取的URL地址,防止重复抓取URL和循环抓取URL,实现URL管理器主要用三种方式,通过内存、数据库、缓存数据库来实现。
  • **网页下载器:**通过传入一个URL地址来下载网页,将网页转换成一个字符串,网页下载器有urllib2(Python官方基础模块)包括需要登录、代理、和cookie,requests(第三方包)
  • **网页解析器:**将一个网页字符串进行解析,可以按照我们的要求来提取出我们有用的信息,也可以根据DOM树的解析方式来解析。网页解析器有正则表达式(直观,将网页转成字符串通过模糊匹配的方式来提取有价值的信息,当文档比较复杂的时候,该方法提取数据的时候就会非常的困难)、html.parser(Python自带的)、beautifulsoup(第三方插件,可以使用Python自带的html.parser进行解析,也可以使用lxml进行解析,相对于其他几种来说要强大一些)、lxml(第三方插件,可以解析 xml 和 HTML),html.parser 和 beautifulsoup 以及 lxml 都是以 DOM 树的方式进行解析的。
  • **应用程序:**就是从网页中提取的有用数据组成的一个应用。

二. Beautiful Soup 介绍

Python爬虫利器之Beautiful Soup的用法

Beautiful Soup 是Python的一个库,最主要的功能是从网页抓取数据。

Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的的解析器,如果我们不安装它, 则Python会使用Python默认的解析器,lxml解析器更加强大,速度更快,推荐安装。

lxml HTML、lxml XML、html5lib的对比,以及beautiful soup的具体使用见网页。

三. Scrapy 介绍

1. 编写Spider

Scrapy入门教程

Spider是用户编写用于从单个网站(或者一些网站)爬取数据的类。

其包含了一个用于下载的初始URL,如何跟进网页中的链接以及如何分析页面中的内容, 提取生成 item 的方法。

为了创建一个Spider,您必须继承 scrapy.Spider 类, 且定义以下三个属性:

  • name: 用于区别Spider。 该名字必须是唯一的,您不可以为不同的Spider设定相同的名字。
  • start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
  • parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。

用于爬取 stackoverflow/tagged/blockchain 的spider实例:

class StackoverflowSpider(scrapy.Spider):
	name = "stackoverflow"
	allowed_domains = ["stackoverflow.com"]
	start_urls = [
		"https://stackoverflow.com/questions/tagged/blockchain"
	]
	#def parse(self, response):

	def __init__(self, *args, **kwargs):
		super(StackoverflowSpider, self).__init__(*args, **kwargs)


	def start_requests(self):

		return [Request(self.start_urls[0],callback=self.first_question)]
			

	def first_question(self, response):
        ......
        yield Request(tag_url,callback=self.question_list)
    
    def question_list(self, response):
    	......
    	yield Request(question_url,
						  callback=self.parse_question)
						  
		next_page = response.xpath('//div[@class="pager fl"]/a[@rel="next"]/@href')
		if next_page is not None:
			next_page_url = 'https://{}{}'.format(self.allowed_domains[0], next_page.extract_first())
			yield Request(next_page_url, 
						  callback=self.question_list) 
						             
    def parse_question(self, response):
        item = StackoverflowItem()
        ......
        yield item

其中:

  • start_requests开始请求网页
  • first_question中的yield Request()请求下一个网页
  • parse_question中的yield item提取item

2. Selectors选择器

Scrapy入门教程

从网页中提取数据有很多方法。Scrapy使用了一种基于 XPath 和 CSS 表达式机制: Scrapy Selectors。 关于selector和其他提取机制的信息请参考 Selector文档 。

如果您想了解的更多,我们推荐 这篇XPath教程 。

为了配合XPath,Scrapy除了提供了 Selector 之外,还提供了方法来避免每次从response中提取数据时生成selector的麻烦。

Selector有四个基本的方法(点击相应的方法可以看到详细的API文档):

  • xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。
  • css(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表.
  • extract(): 序列化该节点为unicode字符串并返回list。
  • re(): 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。

Some tips:

  • url拼接:'https://{}{}'.format(, )
  • .extract()提取所有符合条件的结果的list,.extract_first()提取第一个符合条件的字符串(即list[0])
  • xpath中.//从当前路径开始查找
  • 如果需要提取的一段文字中含有超链接,如

    中含有,可以用.//text()提取文字和超链接的内容

3. 将数据保存到MySQL

Scrapy入门教程

当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。

每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。

以下是item pipeline的一些典型应用:

  • 清理HTML数据
  • 验证爬取的数据(检查item包含某些字段)
  • 查重(并丢弃)
  • 将爬取结果保存到数据库中

编写你自己的item pipeline很简单,每个item pipiline组件是一个独立的Python类,同时必须实现以下方法:

  • process_item(item, spider)

    每个item pipeline组件都需要调用该方法,这个方法必须返回一个 Item (或任何继承类)对象, 或是抛出 DropItem 异常,被丢弃的item将不会被之后的pipeline组件所处理。

此外,他们也可以实现以下方法:

  • open_spider(spider)

    当spider被开启时,这个方法被调用。

  • close_spider(spider)

    当spider被关闭时,这个方法被调用

使用scrapy爬取数据到mysql

为了更好的使用爬虫的api–保证不过时,详细使用流程参考官方文档 https://docs.scrapy.org/en/latest/intro/tutorial.html 。

在python3下使用 pip install PyMySQL安装库确保import pymysql可用即可(此处mysql都py2和py3库名字不一样使用方法页不一样–要注意)。

settings.py中添加如下mysql配置:

ITEM_PIPELINES = {
    'stackoverflow.pipelines.StackoverflowPipeline': 300,
}
MYSQL_HOST = 'localhost'
MYSQL_DBNAME = 'stackoverflow'
MYSQL_USER = 'spider'		#may root
MYSQL_PASSWD = '123456'
MYSQL_PORT = 3306

用于爬取 stackoverflow/tagged/blockchain 的item pipeline实例:

参数 类型 描述
id int 唯一标识
url varchar(255) url链接
url_md5 varchar(255) url的md5
title varchar(255) 标题
description text 正文
tags varchar(255) 标签,以英文";"分割
created_time datetime 创建时间
author varchar(255) 作者
import pymysql

class StackoverflowPipeline(object):
	question_insert = '''insert into question(url, url_md5, title, description, tags, created_time, author)
							values('{url}', '{url_md5}', '{title}', '{description}', '{tags}', '{created_time}', '{author}')'''

	def __init__(self, settings):
		self.settings = settings

	def process_item(self, item, spider):
		url = item['url']
		if url:
			item['url'] = url.strip()

		......

		description = ''
		for desc in item['desc']:
			if desc.strip() == '':
				continue
			description = description + desc.replace('\n', '').replace('  ', ' ')
		description.replace('  ', ' ')
		item['desc'] = description

        ......

		sqltext = self.question_insert.format(
			url = pymysql.escape_string(item['url']),
			......
			author = pymysql.escape_string(item['author']))
		self.cursor.execute(sqltext)

		return item

	@classmethod
	def from_crawler(cls, crawler):
	    return cls(crawler.settings)

	def open_spider(self, spider):
	    # connet database
	    self.connect = pymysql.connect(
	        host=self.settings.get('MYSQL_HOST'),
	        ......
	        passwd=self.settings.get('MYSQL_PASSWD'),
	        charset='utf8',
	        use_unicode=True)

        # insert into database through cursor
	    self.cursor = self.connect.cursor();
	    self.connect.autocommit(True)

	def close_spider(self, spider):
	    self.cursor.close()
	    self.connect.close()

注意:

  • 对item中数据的检查和处理

  • pymysql.escape_string 方法在mysql中保证字符串的转义,避免爬取的字符串包含特殊字符导致sql语句出错!

  • pymysql的具体使用方法尚未了解from_crawler(), self.connect, self.cursor

4. Scrapy中的去重

Scrapy中去重源码与自定义去重

5. 遇到的问题

  1. 爬取 StackOverflow网站,访问被拒绝,在settings.py中添加:
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'stackoverflow (+http://www.yourdomain.com)'
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) ' \
             'AppleWebKit/537.36 (KHTML, like Gecko) ' \
             'Chrome/49.0.2623.87 Safari/537.36'
  1. ignoring response error(429),too many requests

How to handle a 429 Too Many Requests response in Scrapy

Solutions:

  1. Start slow. Reduce the concurrency settings and increase DOWNLOAD_DELAY so you do at max 1 request per second. Then increase these values step by step and see what happens. It might sound paradox, but you might be able to get more items and more 200 response by going slower.
  2. If you are scraping a big site try rotating proxies. The tor network might be a bit heavy handed for this in my experience, so you might try a proxy service like Umair is suggesting

在settings.py中设置:

# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 0.75
  1. url直接复制、抽取的编码问题
#直接复制的结果
"""https://translate.google.cn/#view=home&op=translate&sl=auto&tl=zh-CN&text=%D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80-%D0%94%D0%BE%D1%81%D1%82%D0%BE%D0%B5%D0%B2%D1%81%D0%BA%D0%B8%D0%B9"""

#使用urllib.parse解码
import urllib.parse
russ = '%D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80-%D0%94%D0%BE%D1%81%D1%82%D0%BE%D0%B5%D0%B2%D1%81%D0%BA%D0%B8%D0%B9'
russ1 = urllib.parse.unquote(russ)
print(russ1)

#运行结果
#Александр-Достоевский
  1. 对日期的时区转换
time_old = "2019-03-22 11:07:50"
str_time = datetime.datetime.strptime(time_old, "%Y-%m-%d %H:%M:%S") + datetime.timedelta(hours=8)
time_new = str_time.strftime("%Y-%m-%d %H:%M:%S")

参考

  1. Python爬虫介绍|菜鸟教程
  2. Python爬虫利器之Beautiful Soup的用法
  3. Scrapy入门教程
  4. 使用scrapy爬取数据到mysql
  5. Scrapy中去重源码与自定义去重
  6. How to handle a 429 Too Many Requests response in Scrapy

你可能感兴趣的:(python3,爬虫)