电影排行榜单
1.肖申克的救赎
2.霸王别姬
全称Uniform Resource Locator,即统一资源定位符。
如:https://www.baidu.com/包含了:
访问协议: HTTPS,用于确定数据传输的方式。
服务器名称:www.baidu.com,网站地址。
HTTP协议
全称HyperText Transfer Protocol,即超文本传输协议。HTTP协议以明文方式发送内容,不提供任何方式的数据加密。
HTTPS协议
全称HyperText Transfer Protocol over Secure Socket Layer,即安全套接字层超文本传输协议。HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并对浏览器和服务器之间的通信加密。
HTTP请求过程
请求方法:常见的请求方法有两种:GET和POST
请求的网址:有Host,Cookie,User-Agent等
请求头
请求头 | 说明 |
---|---|
Accept | 浏览器端可以接受的媒体类型。 |
Accept-Encoding | 浏览器接受的编码方式。 |
Accept-Language | 浏览器所接受的语言种类。 |
Connection | 表示是否需要持久连接。 |
Cookie | 网站为了辨别用户身份、进行会话跟踪而储存在用户本地的数据(通常经过加密),由网站服务器创建。 |
Host | 指定被请求资源的Internet主机和端口号,通常从URL中提取出来。 |
User-Agent | 告诉网站服务器,客户端使用的操作系统和浏览器的名称和版本、CPU版本、浏览器渲染引擎、浏览器语言等。 |
请求体:一般承载的内容是POST请求中的表单数据,而对于GET请求,请求体则为空
HTTP响应过程
响应状态码(Response Status Code): 如200代表服务器正常响应,404代表页面未找到
响应头(Response Headers):包含了服务器对请求的应答信息,如Content-Type、Server、Set-Cookie等
响应体(Response Body):包含响应的正文数据,如网页的HTML代码。
用来描述网页的一种语言,全称叫做Hyper Text Markup Language,即超文本标记语言。网页包括文字、按钮、图片和视频等各种复杂的元素,其基础架构就是HTML。
例:movies.html
电影排行1.肖申克的救赎
2.霸王别姬
简称JS,是一种脚本语言。HTML和CSS配合使用,提供给用户的只是一种静态信息,缺乏交互性。我们在网页里可能会看到一些交互和动画效果,如下载进度条、提示框、轮播图等,这通常就是JavaScript的功劳。
HTML定义了网页的结构,但是只有HTML页面的布局并不美观,可能只是简单的节点元素的排列,为了让网页看起来更好看一些,需要CSS的帮助。
全称叫作Cascading Style Sheets,即层叠样式表。“层叠”是指当在HTML中引用了数个样式文件,并且样式发生冲突时,浏览器能依据层叠顺序处理。
# 使用Xpath提取网页
# 导入lxml库的etree模块
from lxml import etree
# 解析movies.html文件,返回一个节点树的对象
html_selector = etree.parse("movies.html",etree.HTMLParser())
root = html_selector.xpath("/html/head/title/text()")
print(root[0])
movies = html_selector.xpath("//p/text()")
print(movies)
XPath全称XML Path Language,即XML路径语言。它是一门在XML文档中查找信息的语言。HTML与XML结构类似,也可以在HTML中查找信息。
安装XPath
>pip install lxml
表达式 | 描述 | 示例 |
---|---|---|
nodename | 选取此节点的所有子节点 | div,p,h1 |
/ | 从根节点选取(描述绝对路径) | /html |
// | 不考虑位置,选取页面中所有子孙节点 | //div |
. | 选取当前节点(描述相对路径) | ./div |
… | 选取当前节点的父节点(描述相对路径) | h1/…/ |
**@**属性名 | 选取属性的值 | @href,@id |
text() | 获取元素中的文本节点 | //h1/text() |
谓语表达式 | 说明 | 结果 |
---|---|---|
//div[@id=‘content’] | 选取属性id为content的div元素 | [ |
//div[@class] | 选取所有带有属性class的div元素 | [ |
//div/p[1]/text() | 选取div节点中的第一个p元素的文本 | [‘1.肖申克的救赎’] |
//div/p[2]/text() | 选取div节点中的第二个p元素的文本 | [‘2.霸王别姬’] |
//div/p[last()]/text() | 选取div节点中的最后一个p元素的文本 | [‘2.霸王别姬’] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fOZkOZCY-1601619554324)(/Users/jack/Library/Application Support/typora-user-images/image-20200814234020395.png)]
引擎(ENGINE)
调度器(SCHEDULER)
下载器(DOWNLOADER)
爬虫(SPIDERS)
项目管道(ITEM PIPELINES)
下载器中间件(Downloader Middlewares)
爬虫中间件(Spider Middlewares)
第①步:爬虫(Spider)使用URL(要爬取页面的网址)构造一个请求(Request)对象,提交给引擎(ENGINE)。如果请求要伪装成浏览器,或者设置代理IP,可以先在爬虫中间件中设置,再发送给引擎。
第②步:引擎将请求安排给调度器,调度器根据请求的优先级确定执行顺序。
第③步:引擎从调度器获取即将要执行的请求。
第④步:引擎通过下载器中间件,将请求发送给下载器下载页面。
第⑤步:页面完成下载后,下载器会生成一个响应(Response)对象并将其发送给引擎。下载后的数据会保存于响应对象中。
第⑥步:引擎接收来自下载器的响应对象后,通过爬虫中间件,将其发送给爬虫(Spider)进行处理。
第⑦步:爬虫将抽取到的一条数据实体(Item)和新的请求(如下一页的链接)发送给引擎。
第⑧步:引擎将从爬虫获取到的Item发送给项目管道(ITEM PIPELINES),项目管道实现数据持久化等功能。同时将新的请求发送给调度器,再从第②步开始重复执行,直到调度器中没有更多的请求,引擎关闭该网站。
>pip install scrapy
创建项目
# 终端
cd 项目文件夹
scrapy startproject 项目名
pycharm打开项目文件夹,在spiders文件夹中新建爬虫文件,如qidian_hot_spider.py
# -*-coding:utf-8-*-
from scrapy import Request
from scrapy.spiders import Spider
class HotSalesSpider(Spider):
name = "hot" # 爬虫名称
start_urls = ["https://www.qidian.com/rank/hotsales?style=1&page=1"]
def parse(self,response): # 数据解析
# 使用xpath定位
list_selector = response.xpath("//div[@class='book-mid-info']")
for one_selector in list_selector:
# 获取小说信息
name = one_selector.xpath("h4/a/text()").extract()[0]
# 获取作者
author = one_selector.xpath("p[1]/a[1]/text()").extract()[0]
# 获取类型
type = one_selector.xpath("p[1]/a[2]/text()").extract()[0]
# 获取形式
form = one_selector.xpath("p[1]/span/text()").extract()[0]
# 定义字典
hot_dict = {
"name":name,
"author":author,
"type":type,
"form":form
}
yield hot_dict #使用yield返回字典
终端中运行,
# 终端
# scrapy crawl 爬虫名称 -o 输出文件
scrapy crawl hot -o hot.csv
Request用来描述一个HTTP请求,它通常在Spider中生成并由下载器执行。
Request的定义形式为:
class scrapy.http.Request(url[,callback,method=‘GET’,headers,body,cookies,meta,encoding=‘utf-8’,priority=0,dont_filter=False,errback])
参数 | 说明 |
---|---|
url | HTTP请求的网址 |
method | HTTP请求的方法,如“GET”、“POST”、“PUT”等,默认为“GET”,必须大写。 |
body | HTTP的请求体,类型为str或unicode。 |
headers | HTTP的请求头,字典型。 |
cookies | 请求的Cookie值,字典型或列表型,可以实现自动登录的效果。 |
encoding | 请求的编码方式,默认为UTF-8。 |
callback | 指定回调函数,即确定页面解析函数,默认为parse()。 |
meta | 字典类型,用于数据的传递。 |
priority | 请求的优先级,默认为0,优先级高的请求优先下载。 |
dont_filter | 如果对同一个url多次提交相同请求,可以使用此项来忽略重复的请求,避免重复下载,默认为False。 |
errback | 在处理请求时引发任何异常时调用的函数。 |
重写start_requests()方法
重写(override)start_requests()方法,手动生成一个功能更强大的Request对象。因为伪装浏览器、自动登录等功能都是在Request对象中设置的。
引擎之所以能自动定位,是因为在Request对象中,指定了解析数据的回调函数(callback),而默认情况下,Request指定的解析函数就是parse()方法,即callback=self.parse
# -*-coding:utf-8-*-
from scrapy import Request
from scrapy.spiders import Spider
class HotSalesSpider(Spider):
name = "hot" # 爬虫名称
qiandian_headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"} # 复制浏览器中request headers中的User-Agent,伪装成浏览器
def start_requests(self): # 重写start_requests()方法
url = "https://www.qidian.com/rank/hotsales?style=1&page=1"
yield Request(url,headers=self.qiandian_headers)
def parse(self,response): # 数据解析
# 使用xpath定位
list_selector = response.xpath("//div[@class='book-mid-info']")
for one_selector in list_selector:
# 获取小说信息
name = one_selector.xpath("h4/a/text()").extract()[0]
# 获取作者
author = one_selector.xpath("p[1]/a[1]/text()").extract()[0]
# 获取类型
type = one_selector.xpath("p[1]/a[2]/text()").extract()[0]
# 获取形式
form = one_selector.xpath("p[1]/span/text()").extract()[0]
# 定义字典
hot_dict = {
"name":name,
"author":author,
"type":type,
"form":form
}
yield hot_dict
Scrapy提取数据有自己的一套机制,被称作选择器(Selector类),它能够自由“选择”由XPath或CSS表达式指定的HTML文档的某些部分。Scrapy的选择器短小简洁、解析快、准确性高,使用其内置的方法可以快速地定位和提取数据。
定位数据:
xpath(query)
css(query)
提取数据:
extract()
extract_first()(SelectorList独有的方法)
re(regex)
re_first(SelectorList独有的方法)
Response用来描述一个HTTP响应,它只是一个基类。当下载器下载完网页后,下载器根据HTTP响应头部的Content-Type自动创建Response的子类对象。子类主要有:
TextResponse
HtmlResponse
XmlResponse
参数 | 说明 |
---|---|
url | 响应的url,只读 |
status | HTTP响应的状态码,如200,403,404。 |
body | HTTP响应体。 |
headers | HTTP的响应头,字典型。 |
meta | 用于接收传递的数据。 |
response接收封装有网页信息的Response对象,这时就可以使用下面的方法实现对数据的定位。
response.selector.xpath(query)
response.selector.css(query)
由于在response中使用XPath、CSS查询十分普遍,因此Response对象提供了两个实用的快捷方式,它们能自动创建选择器并调用选择器的xpath或css方法来定位数据。简化后的方法如下所示:
response.xpath(query)
response.css(query)
CSS全称Cascading Style Sheets,即层叠样式表,用于表现HTML或XML的样式。CSS表达式的语法比XPath简洁,但是功能不如XPath强大,大多作为XPath的辅助。
表达式 | 描述 | 示例 |
---|---|---|
***** | 选取所有元素 | * |
E | 选取E元素 | div |
E1,E2 | 选取E1和E2元素 | div,p |
E1>E2 | 选取E1的子元素E2 | div>h1 |
E1 E2 | 选取E1子孙中的E2元素 | div h1 |
.class | 选取CLASS属性的值为class的元素 | .author |
#id | 选取ID属性的值为id的元素 | #name |
[ATTR] | 选取包含ATTR属性的元素 | [href] |
[ATTR=VALUE] | 选取属性ATTR的值为VALUE的元素 | [class=author] |
E:nth-child(n) | 选取E元素且该元素是其父元素的第n个子元素 | p:nth-child(1) |
E:nth-last-child(n) | 选取E元素且该元素是其父元素的倒数第n个子元素 | p:nth-last-child(1) |
E::text | 获取E元素的文本 | h1::text |
# -*-coding:utf-8-*-
from scrapy import Request
from scrapy.spiders import Spider
class HotSalesSpider(Spider):
name = "hot" # 爬虫名称
qiandian_headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"}
def start_requests(self):
url = "https://www.qidian.com/rank/hotsales?style=1&page=1"
yield Request(url,headers=self.qiandian_headers,callback=self.css_parse)
def css_parse(self,response): # 数据解析
# 使用css定位
list_selector = response.css("[class='book-mid-info']")
for one_selector in list_selector:
# 获取小说信息
name = one_selector.css("h4>a::text").extract()[0]
# 获取作者
author = one_selector.css(".author a::text").extract()[0]
# 获取类型
type = one_selector.css(".author a::text").extract()[1]
# 获取形式
form = one_selector.css(".author span::text").extract()[0]
# 定义字典
hot_dict = {
"name":name,
"author":author,
"type":type,
"form":form
}
yield hot_dict
Item对象是一个简单的容器,用于收集抓取到的数据,其提供了类似于字典(dictionary-like)的API,并具有用于声明可用字段的简单语法。
items.py
import scrapy
#保存小说热销榜字段数据
class QidianHotItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field() #小说名
author = scrapy.Field()#作者
type = scrapy.Field() #类型
form = scrapy.Field() #形式
1.类QidianHotItem继承于Scrapy的Item类。
2.name、author、type和form为小说的各个字段名。
3.scrapy.Field()生成一个Field对象,赋给各自的字段。
4.Field对象用于指定每个字段的元数据,并且Field对象对接受的数据没有任何限制。因此,在定义属性字段时,无需考虑它的数据类型,使用起来非常方便。
# -*-coding:utf-8-*-
from scrapy import Request
from scrapy.spiders import Spider
from qidian_hot.items import QidianHotItem
class HotSalesSpider(Spider):
name = "hot" # 爬虫名称
qiandian_headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"}
def start_requests(self):
url = "https://www.qidian.com/rank/hotsales?style=1&page=1"
yield Request(url,headers=self.qiandian_headers,callback=self.parse)
def parse(self,response): # 数据解析
# 使用xpath定位
list_selector = response.xpath("//div[@class='book-mid-info']")
for one_selector in list_selector:
# 获取小说信息
name = one_selector.xpath("h4/a/text()").extract()[0]
# 获取作者
author = one_selector.xpath("p[1]/a[1]/text()").extract()[0]
# 获取类型
type = one_selector.xpath("p[1]/a[2]/text()").extract()[0]
# 获取形式
form = one_selector.xpath("p[1]/span/text()").extract()[0]
# ITEM封装数据
item = QidianHotItem()
item["name"] = name
item["author"] = author
item["type"] = type
item["form"] = form
yield item
当项目很大,提取的字段数以百计时,数据的提取规则也会越来越多,再加上还要对提取到的数据做转换处理,代码就会变得庞大,维护起来十分困难。
为了解决这个问题,Scrapy提供了项目加载器(ItemLoader)这样一个填充容器。通过填充容器,可以配置Item中各个字段的提取规则,并通过函数分析原始数据,最后对Item字段赋值,使用起来非常便捷。
Item和ItemLoader的区别:
1.Item提供保存抓取到数据的**容器,**需要手动将数据保存于容器中。
2.Itemloader提供的是填充容器的机制。
# -*-coding:utf-8-*-
from scrapy import Request
from scrapy.spiders import Spider
from qidian_hot.items import QidianHotItem
from scrapy.loader import ItemLoader
class HotSalesSpider(Spider):
name = "hot" # 爬虫名称
qiandian_headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"}
def start_requests(self):
url = "https://www.qidian.com/rank/hotsales?style=1&page=1"
yield Request(url,headers=self.qiandian_headers,callback=self.parse)
def parse(self,response): # 数据解析
# 使用xpath定位
list_selector = response.xpath("//div[@class='book-mid-info']")
for one_selector in list_selector:
# 生成ItemLoader实例
novel = ItemLoader(item = QidianHotItem(),selector = one_selector)
novel.add_xpath("name","h4/a/text()")
novel.add_xpath("author","p[1]/a[1]/text()")
novel.add_xpath("type","p[1]/a[2]/text()")
novel.add_xpath("form","p[1]/span/text()")
yield novel.load_item()
当Spider将收集到的数据封装为Item后,它将会被传递到Item Pipeline(项目管道)组件中等待进一步处理。
在Scrapy中,Item Pipeline是可选组件,默认关闭,要想激活它,只需在配置文件settings.py中启用被注释掉的代码即可。
# settings.py
ITEM_PIPELINES = {
'qidian_hot.pipelines.QidianHotPipeline': 300,
}
300是pipeline的优先级,对于多个pipeline,数字越小的优先处理
# pipelines.py
from scrapy.exceptions import DropItem
class QidianHotPipeline:
def __init__(self):
self.author_set = set()
def process_item(self, item, spider): # 接收spider中的item
if item["name"] in self.author_set:
raise DropItem("查找到重复姓名的项目:%s"%item)
return item
# -*-coding:utf-8-*-
from scrapy import Request
from scrapy.spiders import Spider
from qidian_hot.items import QidianHotItem
class HotSalesSpider(Spider):
name = "hot" # 爬虫名称
qiandian_headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"}
current_page = 1
def start_requests(self):
url = "https://www.qidian.com/rank/hotsales?style=1&page=1"
yield Request(url,headers=self.qiandian_headers,callback=self.parse)
def parse(self,response): # 数据解析
# 使用xpath定位
list_selector = response.xpath("//div[@class='book-mid-info']")
for one_selector in list_selector:
# 获取小说信息
name = one_selector.xpath("h4/a/text()").extract()[0]
# 获取作者
author = one_selector.xpath("p[1]/a[1]/text()").extract()[0]
# 获取类型
type = one_selector.xpath("p[1]/a[2]/text()").extract()[0]
# 获取形式
form = one_selector.xpath("p[1]/span/text()").extract()[0]
# ITEM封装数据
item = QidianHotItem()
item["name"] = name
item["author"] = author
item["type"] = type
item["form"] = form
yield item
# 获取下一页url,并生成一个Request请求
self.current_page += 1
if self.current_page <= 25:
next_url = "https://www.qidian.com/rank/hotsales?style=1&page=%d"%self.current_page
yield Request(next_url,callback=self.parse)
要求:https://su.lianjia.com/ershoufang/
1.房屋面积、总价和单价只需要具体的数字,不需要单位名称。
2.删除字段不全的房屋数据,如有的房屋朝向会显示“暂无数据”,应该剔除。
3.保存到CSV文件中的数据,字段要按照如下顺序排列:房屋名称,房屋户型,建筑面积,房屋朝向,装修情况,有无电梯,房屋总价,房屋单价,房屋产权。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AEuiAtrL-1601619554327)(/Users/jack/Library/Application Support/typora-user-images/image-20200815200956787.png)]
items.py
import scrapy
class LianjiaItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field() # 房屋名称
type = scrapy.Field() # 户型
area = scrapy.Field() # 面积
direction = scrapy.Field() # 朝向
fitment = scrapy.Field() # 装修情况
elevator = scrapy.Field() # 电梯
total_price = scrapy.Field() # 总价
unit_price = scrapy.Field() # 单价
property = scrapy.Field() # 产权
pass
lianjia_spider.py
# -*- coding: utf-8 -*-
from scrapy import Request
from scrapy.spiders import Spider
from lianjia.items import LianjiaItem
class HomeSpider(Spider):
name = "home"
current_page = 1
total_page = 0 #总页数
def start_requests(self): # 获取初始请求(start_requests())
url = "https://su.lianjia.com/ershoufang/"
yield Request(url)
def parse(self, response): #实现主页面解析函数(parse())
list_selector = response.xpath("//ul/li/div[@class='info clear']")
for one_selector in list_selector:
try:
# 房屋名称
name = one_selector.xpath("div[@class='flood']/div[@class='positionInfo']/a[1]/text()").extract_first()
# 其他信息
other = one_selector.xpath("div[@class='address']/div[@class='houseInfo']/text()").extract_first()
other_list = other.split("|")
type = other_list[0].strip(" ")
area = other_list[1].strip(" ")
direction = other_list[2].strip(" ")
fitment = other_list[3].strip(" ")
# 价格
price_list = one_selector.xpath("div[@class='priceInfo']//span/text()")
total_price = price_list[0].extract()
unit_price = price_list[1].extract()
item = LianjiaItem()
item["name"] = name.strip(" ")
item["type"] = type
item["area"] = area
item["direction"] = direction
item["fitment"] = fitment
item["total_price"] = total_price
item["unit_price"] = unit_price
# 生成详细页的url
url = one_selector.xpath("div[@class='title']/a/@href").extract_first()
yield Request(url,
meta = {
"item":item},
callback = self.property_parse)
except:
pass
# 获取下一页url
if self.current_page == 1:
#属性page-data的值中包含总页数和当前页。
self.total_page = response.xpath("//div[@class='page-box house-lst-page-box']//@page-data").re("\d+")
self.total_page = int(self.total_page[0]) #获取总页数
self.current_page+=1 #下一页的值
if self.current_page <= self.total_page: #self.total_page:#判断页数是否已越界
next_url = "https://su.lianjia.com/ershoufang/pg%d"%(self.current_page)
yield Request(next_url) #根据URL生成Request,使用yield提交给引擎
def property_parse(self, response): #实现详细页解析函数
property = response.xpath("//div[@class='introContent']/div[@class='transaction']/div[@class='content']/ul/li[2]/span[2]/text()").extract_first()
elevator = response.xpath("//div[@class='introContent']/div[@class='base']/div[@class='content']/ul/li[last()]/text()").extract_first()
item = response.meta["item"]
item["property"] = property
item["elevator"] = elevator
yield item
Pipelines.py
# -*- coding: utf-8 -*-
import re # 正则表达式模块
from scrapy.exceptions import DropItem
class LianjiaPipeline(object):
def process_item(self, item, spider):
# 提取数字
item["area"] = re.findall("\d+\.?\d*",item["area"])[0]
item["unit_price"] = re.findall("\d+\.?\d*",item["unit_price"])[0]
# 剔除数据缺失项
if item["elevator"] == "暂无数据":
raise DropItem("无数据,抛弃此项目:%s"%item)
return item # process_item一定要有return
class CSVPipeline(object):
file = None # 文件对象
index = 0
def open_spider(self,spider): # 爬虫打开时执行打开文件操作
self.file = open("home11.csv","a",encoding="utf-8") #以追加形式打开文件
def process_item(self, item, spider):
if self.index == 0:
column_name = "name,type,area,direction,fitment,elevator,total_price,unit_price,property\n"
self.file.write(column_name)
self.index=1
home_str = item["name"]+","+\
item["type"]+","+\
item["area"]+","+\
item["direction"]+","+\
item["fitment"]+","+ \
item["elevator"] + "," + \
item["total_price"] + "," + \
item["unit_price"] + "," + \
item["property"]+"\n"
self.file.write(home_str) #字符串写入文件中
return item
def close_spider(self,spider): # 爬虫结束时执行关闭文件
self.file.close()
Settings.py
# 伪装浏览器访问
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36'
# 不遵守网站的robots规则
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# 启用两个pipelines
# Configure item pipelines
ITEM_PIPELINES = {
'lianjia.pipelines.LianjiaPipeline': 300,
'lianjia.pipelines.CSVPipeline': 400,
}
运行start.py
from scrapy import cmdline
cmdline.execute("scrapy crawl home".split())
关系型数据库,是建立在关系模型基础上的数据库。简单讲,它由多张能互相联结的二维表格组成,每一行是一条记录,每一列是一个字段,而表就是某个实体的集合,它展现的形式类似于EXCEL中常见的表格。
像SQLite、MySQL、Oracle、SQL Server、DB2等都属于关系型数据库。
https://dev.mysql.com/downloads/
Navicat是一个强大的数据库管理和设计工具。通过直观的GUI(图形用户界面),让用户简单地管理MySQL、MongoDB、SQL Server、Oracle等数据库。
字符集:UTF-8
排序规则:UTF8-unicode-ci
Python访问MySQL数据库:pip install mysqlclient
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uqRyvNsD-1601619554328)(/Users/jack/Library/Application Support/typora-user-images/image-20200821000843506.png)]
调用方法MySQLdb.connect(db,host,user,password,charset)。对应的参数有:
db:数据库名。
host:主机。
user:用户名。
password:密码。
charset:编码格式。
调用Connection对象的cursor()方法,获取操作游标,实现代码如下所示:
db_cursor = db_conn.cursor()
调用Cursor对象的execute()方法,执行SQL语句,实现对数据库的增删改查操作,代码如下所示:
#新增数据
sql=‘insert into hot(name,author,type,form)values(“道君”,“未知”,“仙侠”,“连载”)’
db_cursor.execute(sql)
#修改数据
sql=‘update hot set author = “跃千愁” where name=“道君”’
db_cursor.execute(sql)
#查询表hot中type为仙侠的数据
sql=‘select * from hot where type=“仙侠”’
db_cursor.execute(sql)
#删除表中type为仙侠的数据
sql=‘delete from hot where type=“仙侠”’
db_cursor.execute(sql)
db_conn.rollback()#回滚操作
需要注意的是,回滚操作一定要在commit之前执行,否则就无法恢复了。
调用Connection对象的commit()方法实现数据的提交。
db_conn.commit()
当执行完对数据库的所有操作后,不要忘了关闭游标和数据库对象。
db_cursor.close()#关闭游标
db_conn.close() #关闭数据库
1.配置MySQL数据库信息
2.新建MySQLPipeline类
3.连接MySQL数据库服务器
4.将数据存储于MySQL数据库
5.执行数据库关闭工作
6.启用MySQLPipeline
7.运行爬虫
8.查看结果
navicat中
创建数据库(database):qidian
在数据库下创建表(table):qidianhot
设置表字段:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2RdNBwMH-1601619554331)(/Users/jack/Library/Application Support/typora-user-images/image-20200821001955578.png)]
# pipelines.py
import MySQLdb
from scrapy.exceptions import DropItem
class QidianHotPipeline(object):
def __init__(self):
self.author_set = set()
def process_item(self, item, spider): # 接收spider中的item
if item["name"] in self.author_set:
raise DropItem("查找到重复姓名的项目:%s"%item)
return item
class MySQLPipeline(object):
def open_spider(self,spider): #spider开始时调用一次
db_name = spider.settings.get("MYSQL_DB_NAME","qidianhot")
host = spider.settings.get("MYSQL_HOST","localhost")
user = spider.settings.get("MYSQL_USER","root")
pwd = spider.settings.get("MYSQL_PASSWORD","mysql1234")
# 连接MySQL数据库
self.db_conn = MySQLdb.connect(db=db_name,
host = host,
user=user,
password=pwd,
charset="utf8")
self.db_cursor = self.db_conn.cursor() #得到游标
def process_item(self,item,spider): #处理每一个item
values = (item["name"],item["author"],item["type"],item["form"])
# 确定SQL
sql = "insert into qidianhot(name,author,type,form) values(%s,%s,%s,%s)"
self.db_cursor.execute(sql,values)
return item
def close_spider(self,spider): #spider结束时,调用一次
self.db_conn.commit() # 提交数据
self.db_cursor.close()
self.db_conn.close()
# settings.py
# 激活pipelines
ITEM_PIPELINES = {
'qidian_hot.pipelines.QidianHotPipeline': 300,
'qidian_hot.pipelines.MySQLPipeline': 400,
}
# mysql设置
MYSQL_DB_NAME = "qidian"
MYSQL_HOST = "localhost"
MYSQL_USER = "root"
MYSQL_PASSWORD = "mysql1234"
NoSQL全称Not Only SQL,意即“不仅仅是SQL”,泛指非关系型数据库。传统的关系型数据库使用的是固定模式,并将数据分割到各个表中。然而,对大数据集来说,数据量太大使其难以存放在单一服务器中,此时就需要扩展到多个服务器中。不过,关系型数据库对这种扩展的支持并不够好,因为在查询多个表时,数据可能在不同的服务器中。再者,从网络中爬取的数据,不可避免地会存在数据缺失、结构变化的情况。而具有固定模式的关系型数据库很难适应这种情况。相反,NoSQL具有高性能、高可用性和高伸缩性的特点,可用于超大规模数据的存储,而且无需固定的模式,无需多余操作就可以横向扩展。在NoSQL中,有多种方式可以实现非固定模式和横向发展的功能,它们分别是:
列数据存储:如Hbase数据库。
面向文档存储:如MongoDB数据库。
键值对存储:如Redis数据库。
图形存储:如Neo4j数据库。
SQL | MongoDB | ||
---|---|---|---|
术语 | 英文术语 | 术语 | 英文术语 |
数据库 | database | 数据库 | database |
表 | table | 集合 | collection |
行 | row | 文档 | document |
列 | column | 域 | field |
索引 | index | 索引 | index |
主键 | primary key | 主键 | primary key |
安装访问MongoDB所使用的第三方库pymongo。
pip install pymongo
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yj57iByY-1601619554332)(/Users/jack/Library/Application Support/typora-user-images/image-20200822160821069.png)]
调用方法pymongo.MongoClient()连接MongoDB数据库服务器。连接的地址默认为localhost:27017,也可以手动设置host和port。
import pymongo#导入pymongo库
#方式一:使用默认host和port
db_client = pymongo.MongoClient()
#方式二:自定义host和port
db_client = pymongo.MongoClient(host=“localhost”,port=27017)
#方式三:使用标准的URI连接语法
db_client = pymongo.MongoClient(‘mongodb://localhost:27017/’)
MongoDB可以建立多个数据库,因此需要指定要操作的数据库。以下代码指定了名称为“qidian”的数据库:
db = db_client[“qidian”]
MongoDB中一个数据库可以包含多个集合,这跟关系型数据库中一个数据库有多个表是同一个道理。MongoDB也需要指定要操作的集合。
db_collection = db[“hot”]
(1)插入与条件匹配的单个文档。
如果想添加一个小说文档到集合hot中,可以先将数据存储于字典中,如下所示:
novel={‘name’: ‘太初’, #名称
‘author’: ‘高楼大厦’,#作者
‘form’: ‘连载’, #形式
‘type’: ‘玄幻’ #类型
}
然后调用db_collection的insert_one()方法将新文档插入到集合hot中,实现代码如下所示:
result = db_collection.insert_one(novel)
print(result)
print(result.inserted_id)
(2)插入与条件匹配的所有文档。
还可以使用insert_many()方法,一次插入多个文档,实现代码如下所示:
novel1={‘name’: ‘丰碑杨门’, #名称
‘author’: ‘圣诞稻草人’,#作者
‘form’: ‘连载’, #形式
‘type’: ‘历史’ #类型
}
novel2={‘name’: ‘帝国的崛起’,#名称
‘author’: ‘终极侧位’, #作者
‘form’: ‘连载’, #形式
‘type’: ‘都市’ #类型
}
result = db_collection.insert_many([novel1,novel2])
print(result)
可以使用find_one()或find()方法查询集合中的文档记录。find_one()方法返回单个文档记录,而find()方法则返回一个游标对象,用于查询多个文档记录。
result = db_collection.find_one({“name”:“帝国的崛起”})
print(result)
cursor = db_collection.find({})
cursor = db_collection.find({“type”:“历史”})
可以使用集合的update_one()和update_many()方法实现文档的更新。前者仅更新一个文档;后者可以批量更新多个文档。更新文档的格式如下所示:
{
< operator>: { : , … },
< operator>: { : , … },
…
}
文档中的operator是MongoDB提供的更新操作符,用于指明更新的方式。例如$set表示修改字段值;$unset表示删除指定字段;$rename表示重命名字段。
可以使用集合的delete_one()和delete_many()方法实现文档的删除。前者仅删除一个文档;后者可以批量删除多个文档。
result = db_collection.delete_one({“name”:“太初”})
result = db_collection.delete_many({“type”:“历史”})
当执行完对数据库的所有操作后,不要忘了关闭数据库。
db_client.close()
1.配置可访问的主机(bind)。
bind 127.0.0.1 192.168.64.100 #配置多个IP,IP之间用空格分开
bind 0.0.0.0 #接受任何网络的连接
2.配置监听端口(port)。
Redis默认的监听端口为6379,可以配置自定义的端口号。
port 6379
3.配置密码(requirepass)。
requirepass foobared
4.配置超时时间(timeout)。
timeout 0
5.配置Redis最大内存容量(maxmemory)。
配置Redis最大的内存容量,注意单位是字节。
maxmemory
6.配置数据库的数量(databases)。
配置数据库的数量,默认的数据库数量是16个,数据库的名称为db0~db15。
databases 16
安装访问Redis所需要的第三方库redis-py。
pip install -U redis==2.10.6
redis-py库提供两个类Redis和StrictRedis来实现Redis的命令操作。StrictRedis实现了大部分官方的语法和命令,而Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。这里使用官方推荐的StrictRedis类实现相关操作。
import redis #导入redis模块
#host是redis主机,端口是6379,数据库索引为0,密码为foobared
r = redis.StrictRedis(host=‘localhost’, port=6379,db=0,password=“foobared”)
#将键值对存入redis缓存,key是"name",value是"cathy"
r.set(‘name’, “cathy”)
#取出键name对应的值
print(r[‘name’])
print(r.get(‘name’))
字符串是Redis中最基本的键值对存储形式。它在Redis中是二进制安全的,这意味着它可以接受任何格式的数据,如JPEG图像数据或JSON信息等。
import redis #导入redis模块
#生成StrictRedis对象
r = redis.StrictRedis(host=‘localhost’, #主机
port=6379, #端口
db=0, #数据库索引
password=“foobared”, #密码
decode_responses=True)#设置解码
r.set(‘name’, “cathy”) #将值为"cathy"的字符串赋给键name
r.set(“age”,10) #将10赋给age键
r.setnx(“height”,1.50) #如果键height不存在,则赋给值1.50
r.mset({“score1”:100,“score2”:98}) #批量设置
Redis中的列表是一个双向链表,可以在链表左右分别操作,即支持双向存储。有时也把列表看成一个队列,实现先进先出的功能,所以很多时候将Redis用作消息队列。
r.lpush(“student”,“cathy”,10) #向键为student的列表头部添加值"cathy"和10
r.rpush(“student”,1.50, “女”) #向键为student的列表尾部添加值身高和性别
print(r.lrange(“student”,0,3)) #获取列表student中索引范围是0~3的列表
r.lset(“student”,1,9) #向键为student中索引为1的位置赋值9
r.lpop(“student”) #返回并删除列表student中的首元素
r.rpop(“student”) #返回并删除列表student中的尾元素
r.llen(“student”) #获取student列表长度
print(r.lrange(“student”,0,-1)) #获取列表student中的所有数据
Redis的Set是由非重复的字符串元素组成的无序集合。
#将"cathy",“tom”,“terry”,“lili”,"tom"5个元素添加到键为names的集合中
r.sadd(“names”,“cathy”,“tom”,“terry”,“lili”,“tom”)
r.scard(“names”) #获取键为names的集合中元素个数,结果为4
r.srem(“names”,“tom”) #从键为names的集合中删除"tom"
r.spop(“names”) #从键为names的集合中随机删除并返回该元素
#将"terry"从键为names的集合中转移到键为names1的集合中
r.smove(“names”,“names1”,“terry”)
r.sismember(“names”,“cathy”)#判断"cathy"是否是键为names的集合中的元素,结果为True
r.srandmember(“names”) #随机获取键为names的集合中的一个元素
print(r.smembers(“names”)) #获取键为names的集合中所有元素
Redis的散列表可以看成是具有Key-Value键值对的map容器。Redis的Key-Value结构中,Value也可以存储散列表,而Key可以理解为散列表的表名。
#将key为name,value为cathy的键值对添加到键为stu散列表中
r.hset(“stu”,“name”,“cathy”)
r.hmset(“stu”,{“age”:10,“height”:1.50})#批量添加键值对
r.hsetnx(“stu”,“score”,100) #如果score=100的键值对不存在,则添加
r.hget(“stu”,“name”) #获取散列表中key为name的值
r.hmget(“stu”,[“name”,“age”]) #获取散列表中多个key对应的值
r.hexists(“stu”,“name”) #判断key为name的值是否存在,此处为True
r.hdel(“stu”,“score”) #删除key为score的键值对
r.hlen(“stu”) #获取散列表中键值对个数
r.hkeys(“stu”) #获取散列表中所有的key
与无序集合(Set)一样,有序集合也是由非重复的字符串元素组成的。为了实现对集合中元素的排序,有序集合中每个元素都有一个与其关联的浮点值,称为“分数”。有序集合中的元素按照以下规则进行排序。
①如果元素A和元素B的“分数”不同,则按“分数”的大小排序。
②如果元素A和元素B的“分数”相同,则按元素A和元素B在字典中的排序排列。
当我们发现要爬取的数据不在HTML文档中时,就应该考虑到数据可能会通过JavaScript加载。先来了解一下什么是JavaScript。
JavaScript是互联网上最流行的客户端脚本语言。它运行于用户的浏览器中,被广泛用于Web应用开发。它嵌入于HTML中,常用来为HTML网页添加各种动态功能,为用户提供更流畅美观的浏览效果。
JavaScript能够动态加载文本,并将文本嵌入到HTML文档中。而爬虫只关注JavaScript动态加载的文本,因此,JavaScript的运行机制以及语法结构,仅需了解即可。
QQ音乐是腾讯公司推出的一款网络音乐服务产品,提供海量音乐在线试听、歌词翻译、手机铃声下载、高品质无损音乐试听、音乐下载等服务。图6-1为QQ音乐中流行指数排行榜的页面,网址为https://y.qq.com/n/yqq/toplist/4.html。现要将榜单中的歌曲信息爬取下来,字段有:歌曲名称、唱片、歌手和时长。
终端
cd 项目文件夹
scrapy startproject QQMusic
settings.py模拟浏览器登录
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36'
Items.py创建所需字段,歌曲名称、唱片、歌手和时长
import scrapy
class QqmusicItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
song_name = scrapy.Field()
album_name = scrapy.Field()
singer_name = scrapy.Field()
interval = scrapy.Field()
通过网页分析发现数据不是存储在html中,而是在XHR中的一个cfg文件中,存储形式为json格式
通过json格式检验,发现数据在列表[“detail”][“data”][“songInfoList”]中,
{
"code":0,
"ts":1598418464440,
"start_ts":1598418464432,
"detail":{
"code":0,
"data":{
"data":Object{
...},
"songInfoList":[
{
"id":272124970,
"type":0,
"mid":"0013KFa32c9lVn",
"name":"不爱我",
"title":"不爱我",
"subtitle":"",
"singer":[
{
"id":5062,
"mid":"002J4UUk29y8BY",
"name":"薛之谦",
"title":"薛之谦",
"type":0,
"uin":0
}
],
"album":{
"id":14253042,
"mid":"003tLFOK0wjtKs",
"name":"不爱我",
"title":"不爱我",
"subtitle":"",
"time_public":"2020-08-20",
"pmid":"003tLFOK0wjtKs_1"
},
"mv":{
"id":0,
"vid":"",
"name":"",
"title":"",
"vt":0
},
"interval":264,
"isonly":0,
"language":0,
"genre":1,
"index_cd":0,
"index_album":1,
"time_public":"2020-08-20",
"status":31,
"fnote":0,
"file":{
"media_mid":"0006kNz63gpDYW",
"size_24aac":0,
"size_48aac":1606521,
"size_96aac":3223171,
"size_192ogg":5614611,
"size_192aac":6375652,
"size_128mp3":4231610,
"size_320mp3":10578739,
"size_ape":0,
"size_flac":24981613,
"size_dts":0,
"size_try":0,
"try_begin":64788,
"try_end":95489,
"url":"",
"size_hires":0,
"hires_sample":0,
"hires_bitdepth":0,
"b_30s":0,
"e_30s":0,
"size_96ogg":2945136
},
"pay":{
"pay_month":0,
"price_track":0,
"price_album":0,
"pay_play":0,
"pay_down":0,
"pay_status":0,
"time_free":0
},
"action":{
"switch":605971,
"msgid":0,
"alert":11,
"icons":135752,
"msgshare":0,
"msgfav":0,
"msgdown":0,
"msgpay":0
},
"ksong":{
"id":12343841,
"mid":"003vj5gU0KJcPS"
},
"volume":{
"gain":-7.285,
"peak":1,
"lra":13.44
},
"label":"0",
"url":"",
"bpm":0,
"version":0,
"trace":"",
"data_type":0,
"modify_stamp":0,
"pingpong":"",
"aid":0,
"ppurl":"",
"tid":0,
"ov":0,
"sa":0,
"es":""
},
spiders中创建music_spider.py
# coding : utf-8
from scrapy import Request
from scrapy.spiders import Spider
from QQMusic.items import QqmusicItem
import json
class MusicSpider(Spider):
name = "music"
def start_requests(self):
url = "https://u.y.qq.com/cgi-bin/musics.fcg?-=getUCGI10191787736996072&g_tk=5381&sign=zzaoda95dtf9vst4hxedab8128e78d7233eac3855fff7429013&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data=%7B%22detail%22%3A%7B%22module%22%3A%22musicToplist.ToplistInfoServer%22%2C%22method%22%3A%22GetDetail%22%2C%22param%22%3A%7B%22topId%22%3A4%2C%22offset%22%3A0%2C%22num%22%3A20%2C%22period%22%3A%222020-08-25%22%7D%7D%2C%22comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A0%7D%7D"
yield Request(url)
def parse(self, response):
item = QqmusicItem()
json_text = response.text
music_dict = json.loads(json_text)
for one_music in music_dict["detail"]["data"]["songInfoList"]:
# get song
item["song_name"] = one_music["title"]
# get album
item["album_name"] = one_music["album"]["name"]
# get singer
item["singer_name"] = one_music["singer"][0]["name"]
# get interval
item["interval"] = one_music["interval"]
yield item
Start.py
from scrapy import cmdline
cmdline.execute("scrapy crawl music -o music.csv".split())
Music.csv
album_name,interval,singer_name,song_name
不爱我,264,薛之谦,不爱我
My Boo,169,易烊千玺,My Boo
走下去,209,DP龙猪,走下去
重现8090,209,永彬Ryan.B,异类
乐队的夏天2 第8期,324,木马,后来 (Live)
夏日调色盘,199,树影叶魅,夏日调色盘
可惜我是水瓶座,249,吴业坤,可惜我是水瓶座
陪我去流浪,246,阿悄,海海海
2020中国好声音 第1期,272,潘虹,最好 (Live)
说唱新世代 第1期,203,于贞,她和她和她 (Live)
乐队的夏天2 第8期,271,野孩子,竹枝词 (Live)
Darling早上好,174,Lee A,Darling早上好
Smile,131,Katy Perry,What Makes A Woman (Explicit)
慢慢,239,Uu,慢慢
我想和妳,165,韩子帆,我想和妳在一起
2020中国好声音 第1期,100,李荣浩,谢谢你的爱1999 (Live)
下落不明(完整版),169,王忻辰,下落不明(完整版)
甜甜咸咸,198,你的大表哥曲甲,甜甜咸咸
2020中国好声音 第1期,233,宋宇宁,三巡 (Live)
2020中国好声音 第1期,142,李健,漂浮地铁 (Live)
传统的网页,如果需要更新内容,必须重载整个页面。有了Ajax,便可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
Ajax,全称Asynchronous JavaScript and XML,即异步的JavaScript和XML。它不是新的编程语言,而是利用JavaScript在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容的技术。
现要实现将中国大陆的电影信息爬取下来,字段有:电影名称、导演、演员和评分。网址:https://movie.douban.com/tag/#/
终端
cd 项目文件夹
scrapy startproject Douban
settings.py模拟浏览器登录,取消obey robots
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
Items.py创建需求字段:电影名称、导演、演员和评分
import scrapy
class DoubanItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
directors = scrapy.Field()
casts = scrapy.Field()
rate = scrapy.Field()
pass
url ="https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=0&countries=中国大陆"通过分析url可知,每次请求的url中start以20为基数变化
Douban_spiders.py
# coding: utf-8
from scrapy import Request
from scrapy.spiders import Spider
from Douban.items import DoubanItem
import json
class DoubanSpider(Spider):
name = "douban"
current_page = 1
def start_requests(self):
url = "https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=0&countries=中国大陆"
yield Request(url)
def parse(self, response):
item = DoubanItem()
json_text = response.text
movies_dict = json.loads(json_text)
if len(movies_dict["data"]) == 0:
return
for one_movie in movies_dict["data"]:
item["title"] = one_movie["title"]
item["directors"] = one_movie["directors"]
item["casts"] = one_movie["casts"]
item["rate"] = one_movie["rate"]
yield item
# 抓取下一页信息,直至无下一页,程序报错
url_next = "https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=%d&countries=中国大陆"%(self.current_page*20)
self.current_page += 1
yield Request(url_next)
Start.py
from scrapy import cmdline
cmdline.execute("scrapy crawl douban -o douban.csv".split())
Doubt.csv
casts,directors,rate,title
"徐峥,王传君,周一围,谭卓,章宇",文牧野,9.0,我不是药神
"吕艳婷,囧森瑟夫,瀚墨,陈浩,绿绮",饺子,8.5,哪吒之魔童降世
"周冬雨,易烊千玺,尹昉,周也,吴越",曾国祥,8.3,少年的你
"姜文,葛优,周润发,刘嘉玲,陈坤",姜文,8.8,让子弹飞
"张国荣,张丰毅,巩俐,葛优,英达",陈凯歌,9.6,霸王别姬
"屈楚萧,吴京,李光洁,吴孟达,赵今麦",郭帆,7.9,流浪地球
"黄渤,张译,韩昊霖,杜江,葛优","陈凯歌,张一白,管虎,薛晓路,徐峥,宁浩,文牧野",7.7,我和我的祖国
很多接口地址既冗长又复杂,有的还经过加密甚至还有时效性。例如今日头条的新闻信息的接口地址为https://www.toutiao.com/api/pc/feed/?category=news_hot&utm_source=toutiao&widen=1&max_behot_time=0&max_behot_time_tmp=0&tadrequire=true&as=A195DBDC06ADC25&cp=5BC65DAC62854E1&_signature=ZtN.cQAAPRnM.D.xF5yhvGbTf2,从中很难找出规律,也就无法爬取更多的新闻信息。
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple selenium
安装浏览器驱动程序,需要下载一个Selenium调用浏览器的驱动文件。以Chrome浏览器为例。
1.下载与浏览器版本对应的驱动文件。Chromedriver的下载地址:
官方下载地址:https://chromedriver.storage.googleapis.com/index.html
其他下载地址:http://npm.taobao.org/mirrors/chromedriver/
2.解压出来的文件放入usr/local/bin指定目录下
1.打开一个Finder,然后command+shift+G
2.输入/usr/local/bin
3.下载后解压出来的文件拷贝进入usr/local/bin目录
4.输入chromedriver --version检查一下
3.环境配置
打开终端,输入: cd ~ 回车,会进入~文件夹
然后输入:touch .bash_profile,回车执行后,
再输入:open -e .bash_profile 回车
会在TextEdit中打开这个文件,输入:export PATH=$PATH:/usr/local/bin/ChromeDriver,然后保存
phantomJS是一个无界面浏览器
brew cask install phantomjs
由于Selenium宣称不再支持PhantomJS,因此如果你的Selenium版本较高,运行本项目就会出错。我们建议将PhantomJS换成Firefox浏览器(新版本的Chrome对Selenium也做了限制,不建议使用Chrome浏览器),方法为:
1.下载并安装火狐浏览器(http://www.firefox.com.cn/)
2.下载火狐浏览器对应版本的驱动geckodriver并将geckodriver.exe文件放置到anaconda3/Scripts目录下。
(geckodriver下载https://github.com/mozilla/geckodriver/releases)
3.本项目只需要修改一行代码:
在toutiao_spider.py中,将:
self.driver = webdriver.PhantomJS()
更改为:
self.driver = webdriver.Firefox()
1.声明浏览器对象
Selenium支持很多浏览器,如Chrome、Firefox、IE、Opera、Safari等;也支持Android、BlackBerry等手机端的浏览器;
from selenium import webdriver
driver = webdriver.Chrome() #声明Chrome浏览器对象
driver = webdriver.ie() #声明ie浏览器对象
driver = webdriver.firefox() #声明firefox浏览器对象
driver = webdriver.phantomjs()#声明phantomjs浏览器对象
driver = webdriver.safari() #声明safari浏览器对象
2.访问页面
首先想到的就是使用driver在浏览器中打开一个链接,可以使用get()方法实现:
driver.get("https://www.suning.com/")#请求页面
3.获取页面代码
访问页面后,就可以使用driver的page_source属性获取页面的HMTL代码了:
#获取代码
HTML=driver.page_source
4.定位元素
当获取到HTML代码后,就需要定位到HTML的各个元素,以便提取数据或者对该元素执行诸如输入、点击等操作。WebDriver 提供了大量的方法查询页面中的节点,这些方法形如:find_element_by_*。
以下为Selenium查找单个节点的方法。
find_element_by_id:通过id查找。
find_element_by_name:通过name查。
find_element_by_xpath:通过xpath选择器查找。
find_element_by_link_text:通过链接的文本查找(完全匹配)。
find_element_by_partial_link_text:通过链接的文本查找(部分匹配)。
find_element_by_tag_name:通过标签名查找。
find_element_by_class_name:通过class查找。
find_element_by_css_selector:通过css选择器查找。
5.页面交互
Selenium可以模拟用户对页面执行一系列操作,如输入数据、清除数据、单击按钮等。
from selenium import webdriver
from selenium.webdriver.common.keys
import Keys #导入Keys类
driver = webdriver.Chrome() #声明Chrome浏览器对象
driver.get("https://www.suning.com/") #请求页面
input = driver.find_element_by_id("searchKeywords")#查找节点
input.clear() #清除输入框中默认文字
input.send_keys("iphone") #输入框中输入“iphone”
input.send_keys(Keys.RETURN) #回车功能
6.执行JavaScript
Selenium并未提供所有的页面交互操作方法,例如爬虫中用得最多的下拉页面(用于加载更多内容)。不过Selenium提供了execute_script()方法,用于执行JS,这样我们就可以通过JS代码实现这些操作了。
7.等待页面加载完成
Selenium中跟超时和等待有关的方法主要有三个。
driver的set_page_load_timeout()方法用于设置页面完全加载的超时时间
隐式等待:implicitly_wait()
隐式等待的方法为implicitly_wait(),用于设定一个数据加载的最长等待时间。
显式等待:WebDriverWait类
显性等待使用Selenium的WebDriverWait类,配合该类的until()方法,就能够根据判断条件而进行灵活地等待了。
今日头条热点新闻的页面如图所示,网址为https://www.toutiao.com/ch/news_hot/。
页面默认显示20条热点新闻信息,将页面拉到最底端,会再加载20条信息。因此,如果想要查看更多热点新闻,就必须不断下拉页面。本项目希望使用网络爬虫技术,将尽量多的热点新闻爬取下来保存于CSV文件中。
爬取的字段有:新闻标题、新闻来源和评论数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s7r7MiSr-1601619554334)(/Users/jack/Library/Application Support/typora-user-images/image-20200827220830228.png)]
终端
cd 项目文件夹
scrapy startproject Toutiao
settings.py模拟浏览器登录,取消obey robots,打开Middlewares
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# Enable or disable spider middlewares
SPIDER_MIDDLEWARES = {
'Toutiao.middlewares.ToutiaoSpiderMiddleware': 543,
}
Items.py创建需求字段:新闻标题、新闻来源和评论数。
import scrapy
class ToutiaoItem(scrapy.Item):
title = scrapy.Field()
source = scrapy.Field()
comment = scrapy.Field()
对https://www.toutiao.com/ch/news_hot/网页分析发现,鼠标每次滚动至页面下部时会加载新的新闻条目
middlewares.py
# -*- coding: utf-8 -*-
from scrapy import signals
import time # 导入时间模块
from scrapy.http import HtmlResponse #导入HTML响应模块
from selenium.webdriver.common.by import By #导入By模块
from selenium.webdriver.support.wait import WebDriverWait #导入等待模块
from selenium.webdriver.support import expected_conditions as EC #导入预期条件模块
from selenium.common.exceptions import TimeoutException,NoSuchElementException #导入异常模块
···
def process_request(self, request, spider):
# 判断name是否是toutiao的爬虫
if spider.name == "toutiao":
spider.deiver.get(request.url)
try:
wait = WebDriverWait(spider.driver,5) #设置显式等待,最多5秒
wait.until(EC.presence_of_element_located(By.XPATH,".//div[@class='wcommonFeed']")) #等待新闻列表容器加载完成
time.sleep(5)
spider.driver.execute_script("window.scrollTo(0,document.body.scrollHeight/2)") #使用js的scrollTo方法实现将页面向下滚动到中间
for i in range(10):
time.sleep(5)
spider.driver.execute_script("window.scollTo(0,document.body.scrollHeight)") #使用js的scrollTo方法将页面滚动到最底端
origin_code = spider.driver.page_source #获取加载完成的页面源代码
res = HtmlResponse(url=request.url,encoding="utf-8",body=origin_code,request=request) # 将源代码构造成为一个Response对象,并返回。
return res
except TimeoutException:
print("time out")
except NoSuchElementException:
print("No such element.")
return None
···
url ="https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=0&countries=中国大陆"通过分析url可知,每次请求的url中start以20为基数变化
Douban_spiders.py
# coding: utf-8
from scrapy import Request
from scrapy.spiders import Spider
from Douban.items import DoubanItem
import json
class DoubanSpider(Spider):
name = "douban"
current_page = 1
def start_requests(self):
url = "https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=0&countries=中国大陆"
yield Request(url)
def parse(self, response):
item = DoubanItem()
json_text = response.text
movies_dict = json.loads(json_text)
if len(movies_dict["data"]) == 0:
return
for one_movie in movies_dict["data"]:
item["title"] = one_movie["title"]
item["directors"] = one_movie["directors"]
item["casts"] = one_movie["casts"]
item["rate"] = one_movie["rate"]
yield item
# 抓取下一页信息,直至无下一页,程序报错
url_next = "https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=%d&countries=中国大陆"%(self.current_page*20)
self.current_page += 1
yield Request(url_next)
Start.py
from scrapy import cmdline
cmdline.execute("scrapy crawl douban -o douban.csv".split())
Doubt.csv
casts,directors,rate,title
"徐峥,王传君,周一围,谭卓,章宇",文牧野,9.0,我不是药神
"吕艳婷,囧森瑟夫,瀚墨,陈浩,绿绮",饺子,8.5,哪吒之魔童降世
"周冬雨,易烊千玺,尹昉,周也,吴越",曾国祥,8.3,少年的你
"姜文,葛优,周润发,刘嘉玲,陈坤",姜文,8.8,让子弹飞
"张国荣,张丰毅,巩俐,葛优,英达",陈凯歌,9.6,霸王别姬
"屈楚萧,吴京,李光洁,吴孟达,赵今麦",郭帆,7.9,流浪地球
"黄渤,张译,韩昊霖,杜江,葛优","陈凯歌,张一白,管虎,薛晓路,徐峥,宁浩,文牧野",7.7,我和我的祖国