image.png
前几天由于工作需要,需要抓取一个特定的关键字来提取百度中搜索的结果,并保留50页的数据存入数据库或者.csv文件中。(每天抓取一次)
1.项目需要环境安装
1)scrapy+selenium+chrome(phantomjs)
关于爬虫所依赖环境的安装,我前面已经介绍过了。大家可以参考https://www.jianshu.com/p/1890e9b3ba37这篇文章我的详细介绍即可。
2)mysql+redis安装 数据库安装可查阅百度(很简单)
2.项目开发流程介绍
我们需要模拟用户行为在浏览器的输入框输入指定的关键字,并模拟点击获取想要的数据,将此页面展示的数据进行保存、过滤,并模拟翻页,抓取此关键字的前50页展示,获取我们想要的数据,存入.csv文件或者redis数据库中,以便后面数据分析使用。
3.开发代码详解
1)创建一个scrapy项目
scrapy startproject keyword_scrawl
scrapy genspider redistest baidu.com
代码中各文件介绍
settings.py是一个总配置文件:
BOT_NAME : 工程名字
SPIDER_MODULES:
NEWSPIDER_MODULE:
下面module的配置路径
pipelines.py 这个是一个跟数据存储相关的文件
middlewares.py 可以自定义,让scrapy更加可控
items.py 这个文件有点类似于 django中的一个form,定义了数据保存的格式
,但是它要比django的form应用简单,因为它的字段是十分单一的。
spider文件夹:这个文件夹中存放的是具体的某个网站的爬虫.通过命令行,我们可以创建出属于自己的一个spider。
4.spider代码详解
def make_requests_from_url(self, url): if self.params['st_status'] == 1: return Request(url, meta={'keyword': self.keyword, 'engine':self.sousu, 'phantomjs':True}) else: return Request(url)
首先修改spider中make_requests_from_url函数,添加一个判断,当st_status==1时,我们在返回request对象时,添加一个meta,并在meta中携带我们想要搜索的关键和需要访问的浏览器地址。以及启动phantomjs的指令。
class PhantomJSMiddleware(object): @classmethod def process_request(cls, request, spider): if request.meta.has_key('phantomjs'): keyword = request.meta.get('keyword') dcap = dict(DesiredCapabilities.PHANTOMJS) dcap["phantomjs.page.settings.userAgent"] = ('Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0') dcap["phantomjs.page.settings.AcceptLanguage"] = ('zh-CN,zh;q=0.9') driver = webdriver.PhantomJS(desired_capabilities=dcap) driver.set_page_load_timeout(100) driver.set_script_timeout(15) try: driver.get(request.url) if request.meta.get('engine') == 1: driver.find_element_by_id("query").send_keys(keyword) driver.find_element_by_class_name("swz").click() elif request.meta.get('engine') == 2: driver.find_element_by_id("kw").send_keys(keyword) driver.find_element_by_id("su").click() time.sleep(2) final_url = driver.current_url if final_url != request.url: fullurl = final_url else: fullurl = request.url content = driver.page_source.encode('utf-8','ignore') driver.quit() return HtmlResponse(fullurl, encoding='utf-8', body=content, request=request) except Exception, e: driver.quit() print e
其次修改middlewares中间件中的类方法process_request,此方法默认携带request和spider对象,在我们刚刚修改的make_requests_from_url方法中。我们可以在这里处理前面make_requests_from_url函数返回的Request请求,然后加载selenium、phantomjs,从而获取我们需要访问的浏览器和关键字,此代码会模拟用户的行为去获取关键字内容,然后将页面的内容返回给scrapy.http 中的HtmlResponse对象。这样我们就可以在spider 内的parse函数中获取到刚刚抓取的内容response.body。
# 判断页面的返回状态 if int(response.status) >= 200 and int(response.status) < 400: if not self.params['redis_key']: a_list = response.xpath('//h3/a/@href').extract() for url in a_list: if url.startswith('http://') != True and url.startswith('https://') !=True: url = response.urljoin(url) yield scrapy.Request(url=url, meta={'url':response.url}, callback=self.pang_bo, dont_filter=True) if response.meta.has_key('page') != True and self.sousu == 2: flag = 1 for next_url in response.xpath('//div[@id="page"]/a/@href').extract(): if next_url.startswith('http://') != True and next_url.startswith('https://') !=True: nextUrl = self.start_urls[0] + next_url regex = 'pn=(\d+)' page_number = re.compile(regex).search(nextUrl).group(1) if page_number and flag: flag = 0 # 抓取前50页 for page in range(10,500,10): next_page = 'pn=' + str(page) old_page = re.compile(regex).search(nextUrl).group() nextUrl = nextUrl.replace(old_page, next_page) yield scrapy.Request(url=nextUrl, meta={'page':page}, callback=self.parse)
上面的代码就是获取刚刚网页中展示的每一个搜索结果,并获取到页面规律,模拟翻页50页,将50页的内容全部递交给self.pang_bo函数进行处理。在此做了一个页面去重!
# 处理item def parse_text(self, response): item = {} try: father_url = response.meta["url"] except: father_url = "''" try: item['title'] = response.xpath('//title/text()').extract_first().replace('\r\n','').replace('\n','').encode('utf-8') except: item['title'] = "''" item['url'] = response.url item['domain'] = '' item['crawl_time'] = time.strftime('%Y%m%d%H%M%S') item['keyword'] = '' item['Type_result'] = '' item['type'] = 'html' item['filename'] = 'yq_' + str(int(time.time())) + '_0' + str(rand5())+'.txt' item['referver'] = father_url item['like'] = '' item['transpond'] = '' item['comment'] = '' item['publish_time'] = '' return item def pang_bo(self, response): # 过略掉百度网页 if 'baidu.com' not in response.url and 'ctrip.com' not in response.url and 'baike.com' not in response.url: item = self.parse_text(response) content = soup_text(response.body) if len(content) > 3000: content = content[:3000] #elif len(content) == 0: #yield scrapy.Request(url=response.url, meta={'url':response.url, 'phantomjs':True}, callback=self.pang_bo) body = item['url']+','+item['crawl_time']+','+item['title'].replace(',','') +','+content+'\n' if '正在进入' == item['title']: file_name = os.path.join(self.filetxt,time.strftime('%Y%m%d%H')+'keyword.csv') with open(file_name, 'a') as b: b.write(body) else: filename = os.path.join(self.filetxt,time.strftime('%Y%m%d%H')+'.csv') with open(filename, 'a') as f: f.write(body)# 过滤网页源代码def soup_text(body): try: soup = BeautifulSoup(body, 'lxml') line = re.compile(r'\s+') line = line.sub(r'', soup.body.getText()) p2 = re.compile(u'[^\u4e00-\u9fa5]') # 中GDAC\u4e00\u9fa5 str2 = p2.sub(r'', line) outStr = str2.strip(',') except: outStr = '' return outStr
这个代码主要过略掉一些不需要的网站,然后提取item字段,和页面body(这里过滤了源码)然后将获取到的内容保存到.csv文件中。这只是一个简简单单的爬虫,反爬的话就设置settings如下:
LOG_STDOUT = True # 将进程所有的标准输出(及错误)将会被重定向到log中(为了方便调试)DOWNLOAD_DELAY=0.25 # 下载延时设置 单位秒DOWNLOAD_TIMEOUT = 60 # 下载超时设置(单位秒)CONCURRENT_ITEMS = 200 # 同时处理的itmes数量CONCURRENT_REQUESTS = 16 # 同时并发的请求
作者:可爱的小虫虫
链接:https://www.jianshu.com/p/b29ca072cbed