urllib库的基本使用
官方文档地址: https://docs.python.org/3/library/urllib.html
什么是urllib
是python内置的HTTP请求库 包括以下模块:
- urllib.request: 请求模块
- urllib.error: 异常处理模块
- urllib.parse: url解析模块
- urllib.robotparser:robots.txt解析模块
urllib.request.urlopen
用法: urllib.request.urlopen(url, data=None, [timeoout]*, cafile=None, capth=None, cadefault=False, context=None)
url参数的使用
import urllib.request
response = urllib.request.urlopen('http://www.baidu.com')
print(response.read().decode('utf-8'))
data参数的使用 data是向url发送请求时需要附带的数据,如果提供了data,则是发送post请求,如果为提供data,则发送的是get请求
import urllib.parse, urllib.request
data = bytes(urllib.parse.urlencode({'word':'hello'}), encoding='utf-8')
print(data)
response = urllib.request.urlopen('http://httpbin.org/post',data=data)
print(response.read())
timeout参数的使用 这个参数限定发送请求后等待结果的时间。
import urllib.request
response = urllib.request.urlopen('http://httpbin.org/get', timeout=1)
print(response.read())
当返回结果的时间超过了timeout,会抛出URLError,因此可以捕捉这个错误:
import socket, urllib.request, urllib.error
try:
response = urllib.request.urlopen('http://httpbin.org/get',timeout=0.1)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
响应类型,状态码,响应头
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(type(response))
可以通过response.status, response.getheaders(), response.getheader('server')获取状态码,头部信息
urllib.request
设置headers
from urllib import request, parse
url = 'http://httpbin.org/post'
headers = {
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
'Host': 'httpbin.org'
}
dict = {'name':'zhaofan'}
data = bytes(parse.urlencode(dict), encoding='utf-8')
req = request.Request(url=url,data=data,headers=headers,method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
高级用法--各种handler
代理,ProxyHandler 通过urllib.request.ProxyHandler()可以设置代理。
import urllib.request
proxy_handler = urllib.request.ProxyHandler({
'http': 'http://127.0.0.1:9743',
'https': 'https://127.0.0.1:9743'
})
opener = urllib.request.build_opener(proxy_handler)
response = opener.open('http://httpbin.org/get')
print(response.read())
cookie, HTTPCookieProcessor cookie中保存常见的登录信息, python用http.cookiejar获取cookie及存储cookie:
import http.cookiejar, urllib.request
cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
for item in cookie:
print(item.name+'='+item.value)
cookie可以写入到文件中保存,有两种方式: http.cookiejar.MozillaCookieJar, http.cookiejar.LWPCookieJar:
使用http.cookiejar.MozillaCookieJar()方式:
import http.cookiejar, urllib.request
filename = 'cookie.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
使用http.cookiejar.LWPCookieJar()方式:
import http.cookiejar, urllib.request
filename = 'cookie.txt'
cookie = http.cookiejar.LWPCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
如果想获取文件中的cookie的话,可以通过load的方式:
import http.cookiejar, urllib.request
cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))
URL解析
urllib.parse.urlparse() 用法: urllib.parse.urlparse(url, scheme='', allow_fragments=True)
from urllib.parse import urlparse
result = urlparse('http://www.baidu.com/index.html;user?id=5#comment')
print(result)
Requests库的基本使用
Requests是基于urllib编写的,它比urllib更加方便。
requests功能详解
import requests
response = requests.get('https://www.baidu.com')
requests提供了5中请求方式:
requests.post("http://httpbin.org/post")
requests.put("http://httpbin.org/put")
requests.delete("http://httpbin.org/delete")
requests.head("http://httpbin.org/get")
requests.options("http://httpbin.org/get")
GET请求
import requests
data = {
"name":"zhaofan",
"age":22
}
response = requests.get("http://httpbin.org/get",params=data)
解析json
import requests, json
response = requests.get('http://httpbin.org/get')
print(response.json())
print(json.loads(response.text))
上面两种读取json的方式结果是一样的。
获取二进制数据 可以通过response.content获取二进制数据
添加headers
import requests
headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
}
response = requests.get('https://www.zhihu.com', headers=headers)
POST请求
import requests
data = {
"name":"zhaofan",
"age":23
}
response = requests.post("http://httpbin.org/post",data=data)
print(response.text)
requests高级用法
文件上传
import requests
files= {"files":open("git.jpeg","rb")}
response = requests.post("http://httpbin.org/post",files=files)
print(response.text)
获取cookie
import requests
response = requests.get("http://www.baidu.com")
print(response.cookies)
for key,value in response.cookies.items():
print(key+"="+value)
会话维持
import requests
s = requests.Session()
s.get("http://httpbin.org/cookies/set/number/123456")
response = s.get("http://httpbin.org/cookies")
print(response.text)
证书验证
import requests
from requests.packages import urllib3
urllib3.disable_warnings()
response = requests.get("https://www.12306.cn",verify=False)
print(response.status_code)
代理设置
import requests
proxies= {
"http":"http://127.0.0.1:9999",
"https":"http://127.0.0.1:8888"
}
response = requests.get("https://www.baidu.com",proxies=proxies)
print(response.text)
超时设置 通过timeout参数可以设置超时的时间
认证设置 可以通过requests.auth模块实现
import requests
from requests.auth import HTTPBasicAuth
response = requests.get("http://120.27.34.24:9001/",auth=HTTPBasicAuth("user","123"))
print(response.status_code)
异常处理
import requests
from requests.exceptions import ReadTimeout,ConnectionError,RequestException
try:
response = requests.get("http://httpbin.org/get",timout=0.1)
print(response.status_code)
except ReadTimeout:
print("timeout")
except ConnectionError:
print("connection Error")
except RequestException:
print("error")
正则表达式
匹配模式
\w 匹配字母数字及下划线 \W 匹配f非字母数字下划线 \s 匹配任意空白字符,等价于[\t\n\r\f] \S 匹配任意非空字符 \d 匹配任意数字 \D 匹配任意非数字 \A 匹配字符串开始 \Z 匹配字符串结束,如果存在换行,只匹配换行前的结束字符串 \z 匹配字符串结束 \G 匹配最后匹配完成的位置 \n 匹配一个换行符 \t 匹配一个制表符 ^ 匹配字符串的开头 $ 匹配字符串的末尾 . 匹配任意字符,除了换行符,re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符 [....] 用来表示一组字符,单独列出:[amk]匹配a,m或k [^...] 不在[]中的字符:[^abc]匹配除了a,b,c之外的字符
-
匹配0个或多个的表达式
-
匹配1个或者多个的表达式
? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 {n} 精确匹配n前面的表示 {m,m} 匹配n到m次由前面的正则表达式定义片段,贪婪模式 a|b 匹配a或者b () 匹配括号内的表达式,也表示一个组
使用re.match()
语法格式: re.match(pattern, string, flags=0)
常规匹配
import re
content= "hello 123 4567 World_This is a regex Demo"
result = re.match('^hello\s\d\d\d\s\d{4}\s\w{10}.*Demo$',content)
print(result)
# 获取匹配的结果
print(result.group())
# 获取匹配字符串的长度范围
print(result.span())
使用re.search
扫描整个字符串返回第一个成功匹配的结果
import re
content = "extra things hello 123455 world_this is a Re Extra things"
result = re.search("hello.*?(\d+).*?Re",content)
print(result)
print(result.group())
print(result.group(1))
使用re.findall
搜索字符串,以列表的形式返回全部能匹配的子串
import re
html = '''<div id="songs-list">
<h2 class="title">经典老歌</h2>
<p class="introduction">
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路上有你</li>
<li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li data-view="4" class="active">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</ul>
</div>'''
results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html, re.S)
print(results)
print(type(results))
for result in results:
print(result)
print(result[0], result[1], result[2])
使用re.sub
替换字符串中每一个匹配的子串后返回替换后的字符串 用法: re.sub(正则表达式, 替换成的字符串, 原字符串)
import re
content = "Extra things hello 123455 World_this is a regex Demo extra things"
content = re.sub('\d+','',content)
print(content)
re.compile
将正则表达式编译成正则表达式对象,方便复用该正则表达式
import re
content= """hello 12345 world_this
123 fan
"""
pattern =re.compile("hello.*fan",re.S)
result = re.match(pattern,content)
print(result)
print(result.group())
beautifulsoup库的使用
一个灵活又方便的网页解析库,支持多种解析器,利用它就不用编写正则表达式也能方便的实现网页信息的抓取。
快速使用
from bs4 import BeautifulSoup
html = '''
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
'''
soup = BeautifulSoup(html,'lxml')
print(soup.prettify())
print(soup.title)
print(soup.title.name)
print(soup.title.string)
print(soup.title.parent.name)
print(soup.p)
print(soup.p["class"])
print(soup.a)
print(soup.find_all('a'))
print(soup.find(id='link3'))
for link in soup.find_all('a'):
print(link.get('href'))
print(soup.get_text())
解析器
常见的解析器:
推荐使用xml作为解释器。
基本使用
标签选择器 通过soup.标签名,可以获得这个标签的内容。
获取名称 通过soup.title.name可以获得该title标签的名称。
获取属性 soup.p.attrs['name'] soup.p['name'] 这两种方式都可以获取p标签的name属性
获取内容 soup.p.string 获取第一个p标签的内容
PyQuery库的使用
仿照jquery的网页解析库 http://pyquery.readthedocs.io/en/latest/
基本使用
html = '''
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
print(doc)
print(type(doc))
print(doc('li'))
从URL初始化
from pyquery import PyQuery as pq
doc = pq(url='http://www.baidu.com', encoding='utf-8')
print(doc('head'))
doc2 = pq(filename='index.html')
基本的CSS选择器
参照文档
使用selenium库
selenium是一套完整的web应用程序测试系统,包含了测试的录制,编写及运行和测试的并行处理。它可以模拟真实浏览器,自动化测试工具,支持多种浏览器。
selenium的基本使用
在python爬虫中,主要用的是selenium的WebDriver。
声明浏览器对象
from selenium import webdriver
browser = webdriver.Chrome()
browser = webdriver.Firefox()
访问页面
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('http://www.baidu.com')
print(browser.page_source)
browser.close()
查找元素
from selenium import webdriver
browser = webdriver.Chrome()
browser.get("http://www.taobao.com")
input_first = browser.find_element_by_id("q")
input_second = browser.find_element_by_css_selector("#q")
input_third = browser.find_element_by_xpath('//*[@id="q"]')
print(input_first)
print(input_second)
print(input_third)
browser.close()
这里列举一下常用的查找元素方法:
find_element_by_name find_element_by_id find_element_by_xpath find_element_by_link_text find_element_by_partial_link_text find_element_by_tag_name find_element_by_class_name find_element_by_css_selector
元素交互操作 对于获取的元素调用交互方法
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get("http://www.taobao.com")
input_str = browser.find_element_by_id('q')
input_str.send_keys("ipad")
time.sleep(1)
input_str.clear()
input_str.send_keys("MakBook pro")
button = browser.find_element_by_class_name('btn-search')
button.click()
执行JavaScript
from selenium import webdriver
browser = webdriver.Chrome()
browser.get("http://www.zhihu.com/explore")
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
browser.execute_script('alert("To Bottom")')
隐式等待 到了一定的时间发现元素还没有加载,则继续等待我们指定的时间。 如果超过了我们指定的时间还没有加载就会抛出异常。
from selenium import webdriver
browser = webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://www.zhihu.com/explore')
input = browser.find_element_by_class_name('zu-top-add-question')
print(input)
显示等待 指定一个等待条件,并指定一个最长等待时间,会在这个时间内判断是否满足等待条件。如果条件成立就会立即返回,如果不成立,则一直等待到指定的最长等待时间,超过等待时间就会抛出异常
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
browser = webdriver.Chrome()
browser.get('https://www.taobao.com/')
wait = WebDriverWait(browser, 10)
input = wait.until(EC.presence_of_element_located((By.ID, 'q')))
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search')))
print(input, button)
浏览器的前进和后退 back(), forward()
cookie操作 get_cookies(), delete_all_cookies(), add_cookie()
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'zhaofan'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())
选项卡管理 打开新选项卡: window.open() 不同的选项卡是存在列表里browser.window_handles
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.execute_script('window.open()')
print(browser.window_handles)
browser.switch_to_window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(1)
browser.switch_to_window(browser.window_handles[0])
browser.get('https://python.org')
爬虫的整体思路
Scrapy的初步认识
scrapy使用了twisted作为框架。twisted是事件驱动的,适合异步代码。
创建scrapy工程 scrapy startproject test1 cd test1 scrapy genspider example example.com
items.py代码分析 items.py存放的是要爬取数据的字段信息。
class JoBoleArticleItem(scrapy.Item):
title = scrapy.Field()
create_date = scrapy.Field()
url = scrapy.Field()
url_object_id = scrapy.Field()
front_image_url = scrapy.Field()
front_image_path = scrapy.Field()
praise_nums = scrapy.Field()
fav_nums = scrapy.Field()
comment_nums = scrapy.Field()
tag = scrapy.Field()
content = scrapy.Field()
spiders/Article.py代码分析 spiders目录下的Article.py为主要的爬虫代码,包括了对页面的请求及页面的处理:
class ArticleSpider(scrapy.Spider):
name = "Article"
allowed_domains = ["blog.jobbole.com"]
start_urls = ['http://blog.jobbole.com/all-posts/']
def parse(self, response):
'''
1.获取文章列表也中具体文章url,并交给scrapy进行下载后并进行解析
2.获取下一页的url并交给scrapy进行下载,下载完成后,交给parse
:param response:
:return:
'''
#解析列表页中所有文章的url,并交给scrapy下载后进行解析
post_nodes = response.css("#archive .floated-thumb .post-thumb a")
for post_node in post_nodes:
#image_url是图片的地址
image_url = post_node.css("img::attr(src)").extract_first("")
post_url = post_node.css("::attr(href)").extract_first("")
#这里通过meta参数将图片的url传递进来,这里用parse.urljoin的好处是如果有域名我前面的response.url不生效
# 如果没有就会把response.url和post_url做拼接
yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":parse.urljoin(response.url,image_url)},callback=self.parse_detail)
#提取下一页并交给scrapy下载
next_url = response.css(".next.page-numbers::attr(href)").extract_first("")
if next_url:
yield Request(url=next_url,callback=self.parse)
def parse_detail(self,response):
'''
获取文章的详细内容
:param response:
:return:
'''
article_item = JoBoleArticleItem()
front_image_url = response.meta.get("front_image_url","") #文章封面图地址
title = response.xpath('//div[@class="entry-header"]/h1/text()').extract_first()
create_date = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/text()').extract()[0].strip().split()[0]
tag_list = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/a/text()').extract()
tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
tag =",".join(tag_list)
praise_nums = response.xpath('//span[contains(@class,"vote-post-up")]/h10/text()').extract()
if len(praise_nums) == 0:
praise_nums = 0
else:
praise_nums = int(praise_nums[0])
fav_nums = response.xpath('//span[contains(@class,"bookmark-btn")]/text()').extract()[0]
match_re = re.match(".*(\d+).*",fav_nums)
if match_re:
fav_nums = int(match_re.group(1))
else:
fav_nums = 0
comment_nums =response.xpath("//a[@href='#article-comment']/span/text()").extract()[0]
match_com = re.match(".*(\d+).*",comment_nums)
if match_com:
comment_nums = int(match_com.group(1))
else:
comment_nums=0
content = response.xpath('//div[@class="entry"]').extract()[0]
article_item["url_object_id"] = get_md5(response.url) #这里对地址进行了md5变成定长
article_item["title"] = title
article_item["url"] = response.url
try:
create_date = datetime.datetime.strptime(create_date,'%Y/%m/%d').date()
except Exception as e:
create_date = datetime.datetime.now().date()
article_item["create_date"] = create_date
article_item["front_image_url"] = [front_image_url]
article_item["praise_nums"] = int(praise_nums)
article_item["fav_nums"] = fav_nums
article_item["comment_nums"] = comment_nums
article_item["tag"] = tag
article_item['content'] = content
yield article_item
pipeline中代码分析 pipeline主要是对spiders中爬虫的返回数据处理。
class JobbolespiderPipeline(object):
def process_item(self, item, spider):
return item
class JsonWithEncodingPipeline(object):
'''
返回json数据到文件
'''
def __init__(self):
self.file = codecs.open("article.json",'w',encoding="utf-8")
def process_item(self, item, spider):
lines = json.dumps(dict(item),ensure_ascii=False) + "\n"
self.file.write(lines)
return item
def spider_closed(self,spider):
self.file.close()
class MysqlPipeline(object):
'''
插入mysql数据库
'''
def __init__(self):
self.conn =pymysql.connect(host='192.168.1.19',port=3306,user='root',passwd='123456',db='article_spider',use_unicode=True, charset="utf8")
self.cursor = self.conn.cursor()
def process_item(self,item,spider):
insert_sql = '''
insert into jobbole_article(title,create_date,url,url_object_id,front_image_url,front_image_path,comment_nums,fav_nums,praise_nums,tag,content) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
'''
self.cursor.execute(insert_sql,(item["title"],item["create_date"],item["url"],item["url_object_id"],item["front_image_url"],item["front_image_path"],item["comment_nums"],item["fav_nums"],item["praise_nums"],item["tag"],item["content"]))
self.conn.commit()
class MysqlTwistedPipline(object):
'''
采用异步的方式插入数据
'''
def __init__(self,dbpool):
self.dbpool = dbpool
@classmethod
def from_settings(cls,settings):
dbparms = dict(
host = settings["MYSQL_HOST"],
port = settings["MYSQL_PORT"],
user = settings["MYSQL_USER"],
passwd = settings["MYSQL_PASSWD"],
db = settings["MYSQL_DB"],
use_unicode = True,
charset="utf8",
)
dbpool = adbapi.ConnectionPool("pymysql",**dbparms)
return cls(dbpool)
def process_item(self,item,spider):
'''
使用twisted将mysql插入变成异步
:param item:
:param spider:
:return:
'''
query = self.dbpool.runInteraction(self.do_insert,item)
query.addErrback(self.handle_error)
def handle_error(self,failure):
#处理异步插入的异常
print(failure)
def do_insert(self,cursor,item):
#具体插入数据
insert_sql = '''
insert into jobbole_article(title,create_date,url,url_object_id,front_image_url,front_image_path,comment_nums,fav_nums,praise_nums,tag,content) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
'''
cursor.execute(insert_sql,(item["title"],item["create_date"],item["url"],item["url_object_id"],item["front_image_url"],item["front_image_path"],item["comment_nums"],item["fav_nums"],item["praise_nums"],item["tag"],item["content"]))
class ArticleImagePipeline(ImagesPipeline):
'''
对图片的处理
'''
def item_completed(self, results, item, info):
for ok ,value in results:
if ok:
image_file_path = value["path"]
item['front_image_path'] = image_file_path
else:
item['front_image_path'] = ""
return item
Scrapy框架的架构和原理
scrapy数据流是由执行的核心引擎控制,流程如下:
- 爬虫引擎ENGINE获得初始请求开始抓取。
- 爬虫引擎ENGINE开始请求调度程序SCHEDULER,并准备对下一次的请求进行抓取。
- 爬虫调度器返回下一个请求给爬虫引擎。
- 引擎请求发送到下载器DOWNLOADER,通过下载中间件下载网络数据。
- 一旦下载器完成页面下载,将下载结果返回给爬虫引擎ENGINE。
- 爬虫引擎ENGINE将下载器DOWNLOADER的响应通过中间件MIDDLEWARES返回给爬虫SPIDERS进行处理。
- 爬虫SPIDERS处理响应,并通过中间件MIDDLEWARES返回处理后的items,以及新的请求给引擎。
- 引擎发送处理后的items到项目管道,然后把处理结果返回给调度器SCHEDULER,调度器计划处理下一个请求抓取。
- 重复该过程(继续步骤1),直到爬取完所有的url请求。
各组件介绍
- 爬虫引擎 爬虫引擎负责控制各个组件之间的数据流,当某些操作触发事件后都是通过engine来处理。
- 调度器 调度接收来engine的请求并将请求放入队列中,并通过事件返回给engine。
- 下载器 通过engine请求下载网络数据并将结果响应给engine。
- Spider Spider发出请求,并处理engine返回给它下载器响应数据,以items和规则内的数据请求(urls)返回给engine。
- 管道项目 负责处理engine返回spider解析后的数据,并且将数据持久化,例如将数据存入数据库或者文件。
- 下载中间件 下载中间件是engine和下载器交互组件,以钩子(插件)的形式存在,可以代替接收请求、处理数据的下载以及将结果响应给engine。
- spider中间件 spider中间件是engine和spider之间的交互组件,以钩子(插件)的形式存在,可以代替处理response以及返回给engine items及新的请求集。
Scrapy框架中Spiders用法
我们所有自己写的爬虫都是继承与spider.Spider这个类
name 定义爬虫名字,我们通过命令启动的时候用的就是这个名字,这个名字必须是唯一的
allowed_domains 包含了spider允许爬取的域名列表。当offsiteMiddleware启用时,域名不在列表中URL不会被访问 所以在爬虫文件中,每次生成Request请求时都会进行和这里的域名进行判断
start_urls 起始的url列表 这里会通过spider.Spider方法中会调用start_request循环请求这个列表中每个地址。
custom_settings 自定义配置,可以覆盖settings的配置,主要用于当我们对爬虫有特定需求设置的时候 设置的是以字典的方式设置:custom_settings = {}
from_crawler 这是一个类方法,我们定义这样一个类方法,可以通过crawler.settings.get()这种方式获取settings配置文件中的信息,同时这个也可以在pipeline中使用
start_requests() 这个方法必须返回一个可迭代对象,该对象包含了spider用于爬取的第一个Request请求 这个方法是在被继承的父类中spider.Spider中写的,默认是通过get请求,如果我们需要修改最开始的这个请求,可以重写这个方法,如我们想通过post请求
make_requests_from_url(url) 这个也是在父类中start_requests调用的,当然这个方法我们也可以重写
parse(response) 这个其实默认的回调函数 负责处理response并返回处理的数据以及跟进的url 该方法以及其他的Request回调函数必须返回一个包含Request或Item的可迭代对象
Scrapy矿建中Item Pipeline用法
当Item 在Spider中被收集之后,就会被传递到Item Pipeline中进行处理
每个item pipeline组件是实现了简单的方法的python类,负责接收到item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或者被丢弃而不再进行处理
item pipeline的主要作用:
- 清理html数据
- 验证爬取的数据
- 去重并丢弃
- 讲爬取的结果保存到数据库中或文件中
编写自己的item pipeline
每个item piple组件是一个独立的pyhton类,必须实现以process_item(self,item,spider)方法 每个item pipeline组件都需要调用该方法,这个方法必须返回一个具有数据的dict,或者item对象,或者抛出DropItem异常,被丢弃的item将不会被之后的pipeline组件所处理
下面的方法也可以选择实现
open_spider(self,spider) 表示当spider被开启的时候调用这个方法
close_spider(self,spider) 当spider挂去年比时候这个方法被调用
from_crawler(cls,crawler) 这个和我们在前面说spider的时候的用法是一样的,可以用于获取settings配置文件中的信息,需要注意的这个是一个类方法
启用一个item pipeline组件
在settings配置文件汇总有一个ITEM_PIPELINES的配置参数,如下:
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300,
'myproject.pipelines.JsonWriterPipeline': 800,
}
每个pipeline后面有一个数值,这个值的范围是0-1000,这个数值决定了他们的执行顺序,数字越小越优先。
Scrapy框架中Download Middleware用法
Downloader Middleware处理的过程主要在调度器发送requests请求的时候以及网页将response结果返回给spiders的时候,所以从这里我们可以知道下载中间件是介于Scrapy的request/response处理的钩子,用于修改Scrapy request和response。
在我们的爬虫中,如果要伪装自己的IP,防止网站封IP,可以在middlewares.py中写如下的中间件类:
class ProxyMiddleare(object):
logger = logging.getLogger(__name__)
def process_request(self,request, spider):
self.logger.debug("Using Proxy")
request.meta['proxy'] = 'http://127.0.0.1:9743'
return None
然后在settings.py配置文件中开启下载中间件功能:
DOWNLOADER_MIDDLEWARES={
'httpbintest.middlewares.ProxyMiddleware':543,
}
middleware中的方法说明
process_request(request,spider)
当每个request通过下载中间件时,该方法被调用,这里有一个要求,该方法必须返回以下三种中的任意一种:None,返回一个Response对象,返回一个Request对象或raise IgnoreRequest。三种返回值的作用是不同的。
-
None:Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用,该request被执行(其response被下载)。
-
Response对象:Scrapy将不会调用任何其他的process_request()或process_exception() 方法,或相应地下载函数;其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。
-
Request对象:Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。
-
raise一个IgnoreRequest异常:则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录。
process_response(request, response, spider)
process_response的返回值也是有三种:response对象,request对象,或者raise一个IgnoreRequest异常
-
如果其返回一个Response(可以与传入的response相同,也可以是全新的对象), 该response会被在链中的其他中间件的 process_response() 方法处理。
-
如果其返回一个 Request 对象,则中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。
-
如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。
process_exception(request, exception, spider)
当下载处理器(download handler)或 process_request() (下载中间件)抛出异常(包括 IgnoreRequest 异常)时,Scrapy调用 process_exception()。
process_exception() 也是返回三者中的一个: 返回 None 、 一个 Response 对象、或者一个 Request 对象。
-
如果其返回 None ,Scrapy将会继续处理该异常,接着调用已安装的其他中间件的 process_exception() 方法,直到所有中间件都被调用完毕,则调用默认的异常处理。
-
如果其返回一个 Response 对象,则已安装的中间件链的 process_response() 方法被调用。Scrapy将不会调用任何其他中间件的 process_exception() 方法。
-
如果其返回一个 Request 对象, 则返回的request将会被重新调用下载。这将停止中间件的 process_exception() 方法执行,就如返回一个response的那样。 这个是非常有用的,就相当于如果我们失败了可以在这里进行一次失败的重试,例如当我们访问一个网站出现因为频繁爬取被封ip就可以在这里设置增加代理继续访问
使用middleware 实现随机更换user-agent
from scrapy import signals
class UserAgentMiddleware(object):
"""This middleware allows spiders to override the user_agent"""
def __init__(self, user_agent='Scrapy'):
self.user_agent = user_agent
@classmethod
def from_crawler(cls, crawler):
o = cls(crawler.settings['USER_AGENT'])
crawler.signals.connect(o.spider_opened, signal=signals.spider_opened)
return o
def spider_opened(self, spider):
self.user_agent = getattr(spider, 'user_agent', self.user_agent)
def process_request(self, request, spider):
if self.user_agent:
request.headers.setdefault(b'User-Agent', self.user_agent)
在settings配置文件中配置:
DOWNLOADER_MIDDLEWARES = {
'jobboleSpider.middlewares.RandomUserAgentMiddleware': 543,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
}
RANDOM_UA_TYPE= 'random'
这里我们要将系统的UserAgent中间件设置为None,这样就不会启用,否则默认系统的这个中间会被启用 定义RANDOM_UA_TYPE这个是设置一个默认的值
关于随机切换user-agent的库
github地址为:https://github.com/hellysmile/fake-useragent 安装:pip install fake-useragent
使用例子:
from fake_useragent import UserAgent
ua = UserAgent()
print(ua.ie)
print(ua.chrome)
print(ua.Firefox)
print(ua.random)
print(ua.random)
print(ua.random)
利用规则实现多页面抓取
从start_urls开始,对多页面抓取,可以利用scrapy的规则匹配来自动实现:
# coding=utf-8
__author__ = 'Jeffee Chen'
from scrapy.contrib.spiders import CrawlSpider, Rule
from douban.items import DoubanItem
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
class GroupSpider(CrawlSpider):
name = "Douban"
allowed_domains = ["douban.com"]
start_urls = ["http://www.douban.com/group/explore?tag=%E7%A4%BE%E7%A7%91"]
rules = [
Rule(SgmlLinkExtractor(allow=(r'/group/explore\?start=.*', )), callback='parse_next_page'),
]
def parse_next_page(self, response):
item = DoubanItem()
sel = response.xpath("//div[@class='group-list']/div[@class='result']")
for s in sel:
info = s.xpath("div/div/h3/a/text()").extract()
item["groupName"] = info
yield item
官方文档对rules的解析:
class scrapy.contrib.spiders.Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)
- link_extractor: 是一个 Link Extractor 对象。 其定义了如何从爬取到的页面提取链接。
- callback: 是一个callable或string(该spider中同名的函数将会被调用)。 从link_extractor中每获取到链接时将会调用该函数。该回调函数接受一个response作为其第一个参数, 并返回一个包含 Item 以及(或) Request 对象(或者这两者的子类)的列表(list)。
- cb_kwargs: 包含传递给回调函数的参数(keyword argument)的字典。
- follow: 是一个布尔(boolean)值,指定了根据该规则从response提取的链接是否需要跟进。 如果 callback 为None, follow 默认设置为 True ,否则默认为 False 。
- process_links: 是一个callable或string(该spider中同名的函数将会被调用)。 从link_extractor中获取到链接列表时将会调用该函数。该方法主要用来过滤。
- process_request: 是一个callable或string(该spider中同名的函数将会被调用)。 该规则提取到每个request时都会调用该函数。该函数必须返回一个request或者None。 (用来过滤request)
从论坛里根据时间过滤,抓取每天更新的内容(不使用rules)- 多页抓取
使用rules抓取多页内容,鱿鱼rule进行url提取后只保留了连接地址,失去了上下文内息,不适用这个场景。