[글쓴이:] yoojiseong
[php]php를 이용한 웹크롤링
의존성
composer.json
{
"require": {
"guzzlehttp/guzzle": "^7.0",
"symfony/dom-crawler": "^5.3",
"symfony/css-selector": "^5.3"
}
}
요청과 응답페이지 요소탐색
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
$url = 'http://ujsstudio.com';
$client = new Client();
$res = $client->get($url);
$res = $res->getBody();
$html = (string)$res; // 문자열로 형변환
// dom 필터링
$crawler = new Crawler($html);
$nodeValues = $crawler->filter("#primary a")->each(function(Crawler $node, $i){
return $node->attr('href');
});
페이지네이션 처리
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Symfony\Component\DomCrawler\Crawler;
$result = [];
$client = new Client();
// 페이지 조회 익명함수(제너레이터) 생성
$requests = function ($total) use ($client) {
$uri = 'http://ujsstudio.com/page';
for ($i = 0; $i < $total; $i++) {
yield function() use ($client, $uri, &$i) {
$uri = $uri.'/'.$i+1;
return $client->getAsync($uri);
};
}
};
$pool = new Pool($client, $requests(10), [
'concurrency' => 5,
'fulfilled' => function (Response $response, $index) use (&$result) {
$res = $response->getBody();
$html = (string)$res;
// 요소탐색
$crawler = new Crawler($html);
$nodeValues = $crawler->filter("#primary a")->each(function(Crawler $node, $i){
return $node->attr('href');
});
$result[] = $nodeValues;
},
'rejected' => function (RequestException $reason, $index) {
// this is delivered each failed request
},
]);
$promise = $pool->promise();
$promise->wait();
[laravel] Invalid opcode 117/2/0 오류
php8에서 발생하는 오류 같다.
php.ini 옵션추가 후 valet restart
opcache.optimization_level=0
참고
https://stackoverflow.com/questions/65034752/why-is-the-opcache-not-flushed
[php]stdClass object배열로 변환
function arrayCastRecursive($array)
{
if (is_array($array)) {
foreach ($array as $key => $value) {
if (is_array($value)) {
$array[$key] = arrayCastRecursive($value);
}
if ($value instanceof stdClass) {
$array[$key] = arrayCastRecursive((array)$value);
}
}
}
if ($array instanceof stdClass) {
return arrayCastRecursive((array)$array);
}
return $array;
}
$result = arrayCastRecursive($stdObj);
[docker] redmine 구축하기
docker-compose.yml 작성
https://github.com/jisung87kr/docker-redmine
version: '3.2'
services:
redmine:
image: redmine
restart: always
ports:
- 8080:3000
environment:
REDMINE_DB_MYSQL: db
REDMINE_DB_PASSWORD: PASSWORD
REDMINE_SECRET_KEY_BASE: supersecretkey
db:
image: mysql:5.7
restart: always
ports:
- 3330:3306
environment:
MYSQL_ROOT_PASSWORD: PASSWORD
MYSQL_DATABASE: redmine
volumes:
- db_data:/var/lib/mysql
command: #문자셋 지정
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
db_data:
포트개방
docker-comse.yml 에서 지정한 포트가 개방되어 있어야 한다.
apache 설정
<VirtualHost *:80>
ServerName redmine.ujsstudio.com
#ProxyRequests Off
#ProxyPreserveHost On
ProxyPass / http://localhost:8888/
ProxyPassReverse / http://localhost:8888/
</VirtualHost>
역프록시 설정을 해서 지정한 포트로 리다이렉트 처리
참고
[docker] systemctl 사용할 수 없을때
도커에서 권한 문제로 systemctl 커맨드를 사용할수 없음
privileged 옵션과 /sbin/init 커맨드를 전달해서 해결
docker run -itd --privileged --name centos7 centos:7 /sbin/init
[apache] rewrite mode 설정
$ sudo a2enmod rewrite
$ sudo systemctl restart apache2
Dockerfile에서 자주 쓰이는 명령어
[composer] autoload
composer를 사용해서 간단하게 autoload를 사용하는 방법
1. monolog 패키지 설치해보기
composer require monolog/monolog
2. 패키지 로드하기
index.php
<?php
include './vendor/autoload.php';
use Monolog\Logger;
$log = new Logger('name');
3. 내 클래스 로드해보기
컴포저 autoload는 psr-4 규칙을 따르고 있기 때문에 클래스 생성시 네임스페이스를 규칙에 맞게 정하고 composer.json 파일에 namespace prefix와 클래스를 정의한 디렉토리를 매핑해주는 설정을 추가한다.
composer.json
{
"require": {
"monolog/monolog": "^2.2"
},
"autoload": {
"psr-4": { // Js 네임페이스 접두사를 lib에 매핑
"Js\\" : "lib/"
}
}
}
/lib/src/Foo.php
<?php
namespace Js\Src;
class Foo{
public function say()
{
return 'hello';
}
}
index.php
<?php
include './vendor/autoload.php';
use Monolog\Logger;
use Js\Src\Foo;
$log = new Logger('name');
$foo = new Foo();
echo $foo->say();
composer dump-autoload
Foo 클래스에서 사용한 Js 네임스페이스 접두사를 lib디렉토리에 매핑해 주었기 때문에 autoload가 실행 될때 자동으로 지정한 경로의 클래스를 로드할 수 있게 된다.
참조
- composer.json schema
- psr-4
- http://blog.ujsstudio.com/2021/01/09/psr-4-%eb%84%a4%ec%9e%84%ec%8a%a4%ed%8e%98%ec%9d%b4%ec%8a%a4-%ea%b7%9c%ec%b9%99/
[mysql] 인덱스 정리
인덱스란
- 책의 목차와 같이 테이블 컬럼에 대한 인덱스를 생성해서 데이터를 검색속도를 향상 시키는것
- 인덱스를 타면 풀스캔을 피할 수 있어서 데이터 검색 속도가 빠르다.
풀스캔
- 인덱스가 없는 테이블에서 데이터를 찾을 때 처음부터 마지막 레코드까지 조회하여 검색 조건과 비교하게 된다.
- 많은 양의 데이터를 조회 할 경우 풀스캔은 모둔 레코드를 조회하기 떄문에 성능이 느려지게 된다.
언제쓰면 좋을까
- SELECT 쿼리에서 성능이 잘나오지만 INSERT, UPDATE, DELETE 쿼리에서는 때에 따라 다르다.
- UPDATE, DELETE는 WHERE절에 잘 설정된 인덱스로 조건을 붙여주면 조회할 때 성능은 크게 저하 되지 않는다 (인덱스로 인해 데이터 조회에 속도가 빨라지는 것이고 데이터 수정 자체가 빨라지는 것이 아님)
- INSERT의 경우, 새로운 데이터가 추가되면서 기존 인덱스 페이지에 저장되어 있던 탐색 위치가 수정되어야 하므로 효율이 좋지 않다.
- WHERE 절에서 자주 사용되는 Column
- 외래키가 사용되는 Column
- join에 자주 사용되는 Column
- ORDER BY, ORDER BY
- 데이터양이 많은 테이블
인덱스 칼럼 기준
- 카디널리티가 높은 것
- 전체 행에 대한 특정칼럼의 중복 수치를 나타낸 지표로 복합인덱스인 경우 카디널리티가 높은 순에서 낮은순으로 구성하는게 좋다.
인덱스 사용방법
- 생성
CREATRE INDEX 인덱스이름 ON 테이블이름(필드1, 필드2, ...)
- 삭제
ALTER TABLE 테이블이름 DROP INDEX 인덱스이름
- 실행계획
SHOW INDEX FROM 테이블 이름
구분 | 설명 |
Table | 테이블 이름 |
Non_unique | 인덱스가 중복된 값을 저장할 수 있으면 1, 저장할 수 없으면 0을 표시함 |
Key_name | 인덱스의 이름을 표시하며, 인덱스가 해당 테이블의 기본 키라면 PRIMARY로 표시함 |
Seq_in_index | 인덱스에서의 해당 필드의 순서를 표시함 |
Column_name | 해당 필드의 이름을 표시함 |
Collation | 기본적인 정렬 형태, A오름 차순, NULL: 정렬구분 없음 |
Cardinality | 인덱스에서 저장된 유일한 값들의 수를 표시함 |
Sub_part | 인덱스 접두어를 표시함 |
Packed | 키가 압축되는 방법을 표시함 |
Null | 해당 필드가 Null을 저장 할 수 있으면 YES, 그렇지 않으면 ”를 표시함 |
Index_type | 인덱스에 사용되는 메소드를 표시함 인덱스 모드(BTREE, FULLTEXT, HASH, RTREE) / FULLTEXT 는 5.7 이상에서 유효 |
Comment | 해당 필드를 설명하는 것이 아닌 인덱스에 관한 정보를 표시함 |
Index_comment | 인덱스에 관란 모든 기타 정보를 표시함 |
유의사항
- 최소한 첫번째 인덱스 조건은 조회조건에 포함되어야만 한다.
- 인덱스 컬럼순서와 조회쿼리 컬럼 순서를 지킬필요는 없다.
- 인덱스로 사용된 컬럼값 그대로 사용해야 인덱스가 사용된다.
- 인덱스는 가공된 데이터를 저장하고 있지 않다.
AND
연산자는 검색 범위를 좁혀주지만OR
연산자는 비교할 행이 더 늘기 때문에 풀 스캔이 발생할 확률이 높다.- 범위조건일때 해당 컬럼은 인덱스를 타지만, 그 뒤 인덱스 컬럼들은 인덱스가 사용되지 않는다.
- null 값의 경우 is null 조건으로 인덱스 레인지 스캔 가능
- LIKE 검색시 %가 앞에 위치하면 풀스캔 발생
참조