scrapy+selenium+react+django实现页面信息的爬取与呈现
本学期接到的第二个学习任务:对某页面进行信息爬取,之后将爬取到的信息展示在自己的页面上,因为第一个学习任务是基于Django框架的web端人脸识别注册登录界面的编写,而之后的规划想做前端,所以要求本任务使用react+Django框架。作为一个入门的渣渣,就是一个不断百度的过程,此博客就记录一下,各个部分百度的结果,可以指责但请温柔~
整体的demo
standard_query是Django部分,建立了一个APP:query,然后是前端和爬虫。
selenium爬取动态页面
刚开始接到任务的时候觉得先做爬虫部分,之前跑过一个很简单的爬虫代码,查看源代码—找到所需内容—正则表达式—爬取成功。这个任务和之前的区别在于,我要的信息通过js提交表单之后不跳转页面显示table信息,于是,第一次百度开始了,这部分有N多博客详细介绍而我又只是套路,所以只介绍一下我使用的版本【虽然firefox版本也是百度到的】:Firefox55+geckodriver19.1+selenium(我使用的是当前最新版3.141.0)只能说这一套可以用,因为之前百度的时候看Firefox新版不支持刚好百度到了这个版本可以用,其他的自行尝试。
代码示例:
from selenium import webdriver
import json,re
def getHtml(url):
driver = webdriver.Firefox()
driver.get(url)
driver.find_element_by_xpath('//tfoot/tr/td/select/option[last()]').click()
html = driver.page_source
driver.quit()
return (html)
def getInfo(html):
info_pattern = re.compile(info_href,re.S)
info = re.findall(info_pattern,html)
print(info)
if __name__ == '__main__':
url = 'https://www.tc260.org.cn/front/bzcx/zhcx.html'
info_href = r'<td.*?>(.*?)</td>'
html = getHtml(url)
getInfo(html)
scrapy+Django保存表单信息
真是一个悲伤的故事,开始成功使用webdriver爬取到table信息之后满心欢喜,但是第二步要保存到Django数据库的时候我又懵逼了o(╥﹏╥)o迄今为止我并未使用过一次数据库语言┓( ´∀` )┏上次的任务是直接将ajax异步POST的表单信息new一个模型之后放进去的,但是这次不行了啊,表单信息不是我用Django form类生成的,again,百度,Djangoitem!基于scrapy框架,其中的item使用Djangoitem,直接调用Django里建立的model就可以,之后保存数据的时候只需在pipelines里item.save()即可。结合selenium动态爬取,代码示例如下。
spider,这部分主要是处理response,我对返回的数据依旧以n乘4的形式存入数据库,实现方式很是笨拙,又暂时没有想到其他方法,就先这样了。利用正则找数据而不是xpath是因为到300左右的时候会存在某一项为空的情况,使用hxs.select(…/text)返回的list里该项就会没有任何信息,会打乱item%4的规律,用正则返回值为空,可以使用,当判断页面存在下一页时,使用yield再一次发起request,因为我爬取的是不跳转页面只提交表单,所以URL不变,也因此需要关闭指纹,dont_filter = True。
# -*- coding: utf-8 -*-
import scrapy,re
from scrapy.selector import XmlXPathSelector
from items import StandardItem
i = 0
class StandardSpider(scrapy.spiders.Spider):
name = 'standard'
allowed_domains = ['tc260.org.cn']
start_urls = ['https://www.tc260.org.cn/front/bzcx/zhcx.html']
def parse(self, response):
hxs = XmlXPathSelector(response)
if re.match('https://www.tc260.org.cn/front/bzcx/zhcx.html',response.url):
html = response.body.decode('utf-8')
info_href = r'<td.*?>(.*?)</td>'
info_pattern = re.compile(info_href, re.S)
info = re.findall(info_pattern, html)
td_list = info[:-1]
for index,td in enumerate(td_list):
while index%4 == 0:
item = StandardItem()
item['norm_name'] = td_list[index]
index += 1
if td_list[index].strip() == '':
item['norm_type'] = 'xx'
else:
item['norm_type'] = td_list[index].strip()
index += 1
if td_list[index] == '':
item['norm_admin_name'] = 'xx'
else:
item['norm_admin_name'] = td_list[index]
index += 1
item['norm_co_name'] = td_list[index]
yield item
next_page = hxs.select('//tfoot/tr/td/a[last()-1]/text()').extract()
if next_page[0] == '下一页':
global i
i += 1
yield scrapy.Request(url=response.url,callback=self.parse,meta={'i':i},dont_filter=True)
item,调用Django的model
from scrapy_djangoitem import DjangoItem
from query.models import Standard
class StandardItem(DjangoItem):
django_model = Standard
pipelines,这里只需要对数据进行保存,个人认为更进一步的处理应该有爬取信息与数据库现有信息的对比,否则每次运行他会直接添加进去,但是时间有限,就先这样。
class StandardQuerySplidePipeline(object):
def process_item(self, item, spider):
item.save()
return item
middlewares,这部分是对request进行处理,selenium的作用就是在这,我爬取的内容需要在页面点击提交或者每页内容等才可以在源代码中显现,所以要模拟浏览器驱动。
from selenium import webdriver
from scrapy.http import HtmlResponse
class SeleniumMiddleware:
def process_request(self,request,spider):
option = webdriver.FirefoxOptions()
option.add_argument('headless')
option.add_argument('--user-agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0"')
option.add_argument('--disable-infobars')
driver = webdriver.Firefox(firefox_options=option)
driver.get(request.url)
driver.find_element_by_xpath('//tfoot/tr/td/select/option[last()]').click()
i = request.meta.get('i')
if i == None:
body = driver.page_source
driver.quit()
return HtmlResponse(url=request.url,body=body,request=request,encoding='utf-8',status=200)
else:
while i != 0:
driver.find_element_by_xpath('//tfoot/tr/td/a[last()-2]').click()
i = i-1
body = driver.page_source
driver.quit()
return HtmlResponse(url=request.url, body=body, request=request, encoding='utf-8', status=200)
还好这个数据只有649条,他最多疯狂点击了7次,再大一些就要考虑放弃selenium了(╥╯^╰╥)
最后可以在main里加一条语句,省的每次打开cmd
from scrapy import cmdline
cmdline.execute('scrapy crawl standard --nolog'.split())
react+Django跨域问题
axios+corsheaders,这一部分真真是完全懵逼脸,百度出结果也完全不懂为什么,反正能用,这一部分的配置建议查看我参考的博客:https://blog.csdn.net/weixin_33912638/article/details/87587514
具体使用查看我参考的另一篇博客:
https://blog.csdn.net/u013210620/article/details/79917198
感觉写的很好,毕竟让我这个小白成功的进行了数据的交互。
react部分的配置,axios发起请求,baseURL是Django的默认地址。
axios.defaults.withCredentials = true;
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.baseURL = 'http://127.0.0.1:8000';
调用的时候:
await axios.get('/query/',{params:param})
.then(response => {console.log(response.data);
this.getDataRead(response);
})
.catch(error => console.log(error))
成功/失败之后的操作随意一些,我懒得改直接粘了我的源码,嗯,源码也很随意
前端调用,后端就要有相应的URL。
项目下的urls文件:
from django.contrib import admin
from django.urls import re_path,include
urlpatterns = [
re_path('admin/', admin.site.urls),
re_path(r'query/',include('query.urls')),
]
APP下的urls文件:
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r'^$',views.query),
]
处理URL的request时,返回obj,添加Access-Control-Allow-Origin
from django.http import HttpResponse
from .models import Standard
import json
def query(request):
params = request.GET.get('name')
standard_list = Standard.objects.filter(norm_name__contains=params) | Standard.objects.filter(norm_type__contains=params) | Standard.objects.filter(norm_admin_name__contains=params) | Standard.objects.filter(norm_co_name__contains=params)
queryout_list = []
for index,standard in enumerate(standard_list):
#queryout_list.append({'项目名称':standard.norm_name,'项目类型':standard.norm_type,'主要研制人':standard.norm_admin_name,'承担单位':standard.norm_co_name})
queryout_list.append([standard.norm_name,standard.norm_type,standard.norm_admin_name,standard.norm_co_name])
queryout = json.dumps(queryout_list)
obj = HttpResponse(queryout)
obj['Access-Control-Allow-Origin'] = 'http://localhost:3000'
return obj
为了使后端接收到request,需要corsheaders,这个在Django的settings文件里进行配置,这个CORS_ORIGIN_WHITELIST就是react默认的地址
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = ('loaclhost:3000')
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
)
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
)
然后把他加入到APP中:
INSTALLED_APPS = [
'query',
'corsheaders', #跨域问题的解决
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
还有middleware,这有一个顺序问题,就是cors要放到common前边。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
#'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
react
前端这一块是我接下来要学习的部分,我就写了一个input框,一个button点击搜索,加一个用来展示结果的初步假设是table但最后不算table的table,也没什么好说的,。这里要说的就是一点,我第一次使用的时候,npm start成功打开了网页,后边也成功了两次,但是在这个项目里,我新建之后并没能启动,这个卡了我一天,最后发现是react没在环境变量里,emmm不知道是什么时候安装或者卸载或者怎么的不在了,反正就在环境变量里填上之后就可以了,报错的截图
上代码:
import React,{Component} from 'react';
import json from 'json3';
import axios from 'axios';
//发起请求
import './Query.css';
axios.defaults.withCredentials = true;
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.baseURL = 'http://127.0.0.1:8000';
//const server = 'http://127.0.0.1:8000';
class Query extends Component {
constructor(props) {
super(props);
this.state = {
array:[],
inputValue:''
}
this.handelInputChange = this.handelInputChange.bind(this);
this.handelBtnClick = this.handelBtnClick.bind(this);
}
handelInputChange(e) {
this.setState(
{
inputValue:e.target.value
})
}
getDataRead(response) {
var len = response.data.length
for(var i=0;i<len;i++){
this.setState(
{
array:[...this.state.array,response.data[i]]
});
}
}
getDataWrite(response) {
return (
this.state.array.map((item,index) =>
{
return(
<tr key = {index}>
<td id = 'fr'>{this.state.array[index][0]}</td>
<td id = 'se'>{this.state.array[index][1]}</td>
<td id = 'thi'>{this.state.array[index][2]}</td>
<td id = 'fou'>{this.state.array[index][3]}</td>
</tr>
)
})
)
}
async handelBtnClick() {
let param = {
'name':this.state.inputValue
}
this.state.array.length = 0
await axios.get('/query/',{params:param})
.then(response => {console.log(response.data);
this.getDataRead(response);
})
.catch(error => console.log(error))
}
render() {
return(
<div>
<input value = {this.state.inputValue} onChange = {this.handelInputChange}/>
<button onClick = {this.handelBtnClick}>搜索</button>
<table>
<thead>
<tr className = 'table_com_bg'>
<th id = 'frti'>项目名称</th>
<th id = 'seti'>项目类型</th>
<th id = 'thiti'>主要研制人</th>
<th id = 'fouti'>承担单位</th>
</tr>
</thead>
<tbody>
{this.getDataWrite()}
</tbody>
</table>
</div>
);
}
}
export default Query;
基本就是这些,纪念一下我的第二个学习任务,感觉还有很多需要完善的地方,比如爬取到的信息添加到数据库时item的筛选,是否需要定时爬取,代替selenium的工具,前端的css编写,balabala,我先学习别的了,挥手┏(^0^)┛